Skip to content

Commit

Permalink
refactor: [#157] extract API contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Mar 10, 2023
1 parent 8e387cd commit 19d33b4
Show file tree
Hide file tree
Showing 28 changed files with 364 additions and 268 deletions.
46 changes: 46 additions & 0 deletions src/apis/context/auth_key/handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use axum::extract::{Path, State};
use axum::response::Response;
use serde::Deserialize;

use super::responses::{
auth_key_response, failed_to_delete_key_response, failed_to_generate_key_response, failed_to_reload_keys_response,
};
use crate::apis::context::auth_key::resources::AuthKey;
use crate::apis::responses::{invalid_auth_key_param_response, ok_response};
use crate::tracker::auth::Key;
use crate::tracker::Tracker;

pub async fn generate_auth_key_handler(State(tracker): State<Arc<Tracker>>, Path(seconds_valid_or_key): Path<u64>) -> Response {
let seconds_valid = seconds_valid_or_key;
match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await {
Ok(auth_key) => auth_key_response(&AuthKey::from(auth_key)),
Err(e) => failed_to_generate_key_response(e),
}
}

#[derive(Deserialize)]
pub struct KeyParam(String);

pub async fn delete_auth_key_handler(
State(tracker): State<Arc<Tracker>>,
Path(seconds_valid_or_key): Path<KeyParam>,
) -> Response {
match Key::from_str(&seconds_valid_or_key.0) {
Err(_) => invalid_auth_key_param_response(&seconds_valid_or_key.0),
Ok(key) => match tracker.remove_auth_key(&key.to_string()).await {
Ok(_) => ok_response(),
Err(e) => failed_to_delete_key_response(e),
},
}
}

pub async fn reload_keys_handler(State(tracker): State<Arc<Tracker>>) -> Response {
match tracker.load_keys_from_database().await {
Ok(_) => ok_response(),
Err(e) => failed_to_reload_keys_response(e),
}
}
4 changes: 4 additions & 0 deletions src/apis/context/auth_key/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod handlers;
pub mod resources;
pub mod responses;
pub mod routes;
File renamed without changes.
35 changes: 35 additions & 0 deletions src/apis/context/auth_key/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::error::Error;

use axum::http::{header, StatusCode};
use axum::response::{IntoResponse, Response};

use crate::apis::context::auth_key::resources::AuthKey;
use crate::apis::responses::unhandled_rejection_response;

/// # Panics
///
/// Will panic if it can't convert the `AuthKey` resource to json
#[must_use]
pub fn auth_key_response(auth_key: &AuthKey) -> Response {
(
StatusCode::OK,
[(header::CONTENT_TYPE, "application/json; charset=utf-8")],
serde_json::to_string(auth_key).unwrap(),
)
.into_response()
}

#[must_use]
pub fn failed_to_generate_key_response<E: Error>(e: E) -> Response {
unhandled_rejection_response(format!("failed to generate key: {e}"))
}

#[must_use]
pub fn failed_to_delete_key_response<E: Error>(e: E) -> Response {
unhandled_rejection_response(format!("failed to delete key: {e}"))
}

#[must_use]
pub fn failed_to_reload_keys_response<E: Error>(e: E) -> Response {
unhandled_rejection_response(format!("failed to reload keys: {e}"))
}
25 changes: 25 additions & 0 deletions src/apis/context/auth_key/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use std::sync::Arc;

use axum::routing::{get, post};
use axum::Router;

use super::handlers::{delete_auth_key_handler, generate_auth_key_handler, reload_keys_handler};
use crate::tracker::Tracker;

pub fn add(router: Router, tracker: Arc<Tracker>) -> Router {
// Keys
router
.route(
// code-review: Axum does not allow two routes with the same path but different path variable name.
// In the new major API version, `seconds_valid` should be a POST form field so that we will have two paths:
// POST /api/key
// DELETE /api/key/:key
"/api/key/:seconds_valid_or_key",
post(generate_auth_key_handler)
.with_state(tracker.clone())
.delete(delete_auth_key_handler)
.with_state(tracker.clone()),
)
// Keys command
.route("/api/keys/reload", get(reload_keys_handler).with_state(tracker))
}
2 changes: 1 addition & 1 deletion src/apis/resources/mod.rs → src/apis/context/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub mod auth_key;
pub mod peer;
pub mod stats;
pub mod torrent;
pub mod whitelist;
13 changes: 13 additions & 0 deletions src/apis/context/stats/handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use std::sync::Arc;

use axum::extract::State;
use axum::response::Json;

use super::resources::Stats;
use super::responses::stats_response;
use crate::tracker::services::statistics::get_metrics;
use crate::tracker::Tracker;

pub async fn get_stats_handler(State(tracker): State<Arc<Tracker>>) -> Json<Stats> {
stats_response(get_metrics(tracker.clone()).await)
}
4 changes: 4 additions & 0 deletions src/apis/context/stats/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod handlers;
pub mod resources;
pub mod responses;
pub mod routes;
File renamed without changes.
8 changes: 8 additions & 0 deletions src/apis/context/stats/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use axum::response::Json;

use super::resources::Stats;
use crate::tracker::services::statistics::TrackerMetrics;

pub fn stats_response(tracker_metrics: TrackerMetrics) -> Json<Stats> {
Json(Stats::from(tracker_metrics))
}
11 changes: 11 additions & 0 deletions src/apis/context/stats/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::sync::Arc;

use axum::routing::get;
use axum::Router;

use super::handlers::get_stats_handler;
use crate::tracker::Tracker;

pub fn add(router: Router, tracker: Arc<Tracker>) -> Router {
router.route("/api/stats", get(get_stats_handler).with_state(tracker))
}
59 changes: 59 additions & 0 deletions src/apis/context/torrent/handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;

use axum::extract::{Path, Query, State};
use axum::response::{IntoResponse, Json, Response};
use serde::{de, Deserialize, Deserializer};

use super::resources::torrent::ListItem;
use super::responses::{torrent_info_response, torrent_list_response, torrent_not_known_response};
use crate::apis::responses::invalid_info_hash_param_response;
use crate::apis::InfoHashParam;
use crate::protocol::info_hash::InfoHash;
use crate::tracker::services::torrent::{get_torrent_info, get_torrents, Pagination};
use crate::tracker::Tracker;

pub async fn get_torrent_handler(State(tracker): State<Arc<Tracker>>, Path(info_hash): Path<InfoHashParam>) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => invalid_info_hash_param_response(&info_hash.0),
Ok(info_hash) => match get_torrent_info(tracker.clone(), &info_hash).await {
Some(info) => torrent_info_response(info).into_response(),
None => torrent_not_known_response(),
},
}
}

#[derive(Deserialize)]
pub struct PaginationParams {
#[serde(default, deserialize_with = "empty_string_as_none")]
pub offset: Option<u32>,
pub limit: Option<u32>,
}

pub async fn get_torrents_handler(
State(tracker): State<Arc<Tracker>>,
pagination: Query<PaginationParams>,
) -> Json<Vec<ListItem>> {
torrent_list_response(
&get_torrents(
tracker.clone(),
&Pagination::new_with_options(pagination.0.offset, pagination.0.limit),
)
.await,
)
}

/// Serde deserialization decorator to map empty Strings to None,
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: fmt::Display,
{
let opt = Option::<String>::deserialize(de)?;
match opt.as_deref() {
None | Some("") => Ok(None),
Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some),
}
}
4 changes: 4 additions & 0 deletions src/apis/context/torrent/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod handlers;
pub mod resources;
pub mod responses;
pub mod routes;
2 changes: 2 additions & 0 deletions src/apis/context/torrent/resources/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod peer;
pub mod torrent;
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ mod tests {

use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};

use crate::apis::resources::peer::Peer;
use crate::apis::resources::torrent::{ListItem, Torrent};
use super::Torrent;
use crate::apis::context::torrent::resources::peer::Peer;
use crate::apis::context::torrent::resources::torrent::ListItem;
use crate::protocol::clock::DurationSinceUnixEpoch;
use crate::protocol::info_hash::InfoHash;
use crate::tracker::peer;
Expand Down
18 changes: 18 additions & 0 deletions src/apis/context/torrent/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use axum::response::{IntoResponse, Json, Response};
use serde_json::json;

use super::resources::torrent::{ListItem, Torrent};
use crate::tracker::services::torrent::{BasicInfo, Info};

pub fn torrent_list_response(basic_infos: &[BasicInfo]) -> Json<Vec<ListItem>> {
Json(ListItem::new_vec(basic_infos))
}

pub fn torrent_info_response(info: Info) -> Json<Torrent> {
Json(Torrent::from(info))
}

#[must_use]
pub fn torrent_not_known_response() -> Response {
Json(json!("torrent not known")).into_response()
}
17 changes: 17 additions & 0 deletions src/apis/context/torrent/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use std::sync::Arc;

use axum::routing::get;
use axum::Router;

use super::handlers::{get_torrent_handler, get_torrents_handler};
use crate::tracker::Tracker;

pub fn add(router: Router, tracker: Arc<Tracker>) -> Router {
// Torrents
router
.route(
"/api/torrent/:info_hash",
get(get_torrent_handler).with_state(tracker.clone()),
)
.route("/api/torrents", get(get_torrents_handler).with_state(tracker))
}
46 changes: 46 additions & 0 deletions src/apis/context/whitelist/handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::str::FromStr;
use std::sync::Arc;

use axum::extract::{Path, State};
use axum::response::Response;

use super::responses::{
failed_to_reload_whitelist_response, failed_to_remove_torrent_from_whitelist_response, failed_to_whitelist_torrent_response,
};
use crate::apis::responses::{invalid_info_hash_param_response, ok_response};
use crate::apis::InfoHashParam;
use crate::protocol::info_hash::InfoHash;
use crate::tracker::Tracker;

pub async fn add_torrent_to_whitelist_handler(
State(tracker): State<Arc<Tracker>>,
Path(info_hash): Path<InfoHashParam>,
) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => invalid_info_hash_param_response(&info_hash.0),
Ok(info_hash) => match tracker.add_torrent_to_whitelist(&info_hash).await {
Ok(_) => ok_response(),
Err(e) => failed_to_whitelist_torrent_response(e),
},
}
}

pub async fn remove_torrent_from_whitelist_handler(
State(tracker): State<Arc<Tracker>>,
Path(info_hash): Path<InfoHashParam>,
) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => invalid_info_hash_param_response(&info_hash.0),
Ok(info_hash) => match tracker.remove_torrent_from_whitelist(&info_hash).await {
Ok(_) => ok_response(),
Err(e) => failed_to_remove_torrent_from_whitelist_response(e),
},
}
}

pub async fn reload_whitelist_handler(State(tracker): State<Arc<Tracker>>) -> Response {
match tracker.load_whitelist_from_database().await {
Ok(_) => ok_response(),
Err(e) => failed_to_reload_whitelist_response(e),
}
}
3 changes: 3 additions & 0 deletions src/apis/context/whitelist/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod handlers;
pub mod responses;
pub mod routes;
20 changes: 20 additions & 0 deletions src/apis/context/whitelist/responses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use std::error::Error;

use axum::response::Response;

use crate::apis::responses::unhandled_rejection_response;

#[must_use]
pub fn failed_to_remove_torrent_from_whitelist_response<E: Error>(e: E) -> Response {
unhandled_rejection_response(format!("failed to remove torrent from whitelist: {e}"))
}

#[must_use]
pub fn failed_to_whitelist_torrent_response<E: Error>(e: E) -> Response {
unhandled_rejection_response(format!("failed to whitelist torrent: {e}"))
}

#[must_use]
pub fn failed_to_reload_whitelist_response<E: Error>(e: E) -> Response {
unhandled_rejection_response(format!("failed to reload whitelist: {e}"))
}
22 changes: 22 additions & 0 deletions src/apis/context/whitelist/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::sync::Arc;

use axum::routing::{delete, get, post};
use axum::Router;

use super::handlers::{add_torrent_to_whitelist_handler, reload_whitelist_handler, remove_torrent_from_whitelist_handler};
use crate::tracker::Tracker;

pub fn add(router: Router, tracker: Arc<Tracker>) -> Router {
router
// Whitelisted torrents
.route(
"/api/whitelist/:info_hash",
post(add_torrent_to_whitelist_handler).with_state(tracker.clone()),
)
.route(
"/api/whitelist/:info_hash",
delete(remove_torrent_from_whitelist_handler).with_state(tracker.clone()),
)
// Whitelist commands
.route("/api/whitelist/reload", get(reload_whitelist_handler).with_state(tracker))
}
Loading

0 comments on commit 19d33b4

Please sign in to comment.