Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract new package: Tracker API client #1179

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ jobs:
cargo publish -p bittorrent-http-protocol
cargo publish -p bittorrent-tracker-client
cargo publish -p torrust-tracker
cargo publish -p torrust-tracker-api-client
cargo publish -p torrust-tracker-client
cargo publish -p torrust-tracker-clock
cargo publish -p torrust-tracker-configuration
Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ rust-version.workspace = true
version.workspace = true

[lib]
name = "torrust_tracker_lib"
name = "torrust_tracker_lib"

[workspace.package]
authors = ["Nautilus Cyberneering <info@nautilus-cyberneering.de>, Mick van Dijke <mick@dutchbits.nl>"]
Expand Down Expand Up @@ -97,6 +97,7 @@ ignored = ["crossbeam-skiplist", "dashmap", "figment", "parking_lot", "serde_byt
[dev-dependencies]
local-ip-address = "0"
mockall = "0"
torrust-tracker-api-client = { version = "3.0.0-develop", path = "packages/tracker-api-client" }
torrust-tracker-test-helpers = { version = "3.0.0-develop", path = "packages/test-helpers" }

[workspace]
Expand All @@ -108,6 +109,7 @@ members = [
"packages/primitives",
"packages/test-helpers",
"packages/torrent-repository",
"packages/tracker-api-client",
"packages/tracker-client",
]

Expand Down
23 changes: 23 additions & 0 deletions packages/tracker-api-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
description = "A library to interact with the Torrust Tracker REST API."
keywords = ["bittorrent", "client", "tracker"]
license = "LGPL-3.0"
name = "torrust-tracker-api-client"
readme = "README.md"

authors.workspace = true
documentation.workspace = true
edition.workspace = true
homepage.workspace = true
publish.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true

[dependencies]
hyper = "1"
reqwest = { version = "0", features = ["json"] }
serde = { version = "1", features = ["derive"] }
thiserror = "2"
url = { version = "2", features = ["serde"] }
uuid = { version = "1", features = ["v4"] }
23 changes: 23 additions & 0 deletions packages/tracker-api-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Torrust Tracker API Client

A library to interact with the Torrust Tracker REST API.

## License

**Copyright (c) 2024 The Torrust Developers.**

This program is free software: you can redistribute it and/or modify it under the terms of the [GNU Lesser General Public License][LGPL_3_0] as published by the [Free Software Foundation][FSF], version 3.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU Lesser General Public License][LGPL_3_0] for more details.

You should have received a copy of the *GNU Lesser General Public License* along with this program. If not, see <https://www.gnu.org/licenses/>.

Some files include explicit copyright notices and/or license notices.

### Legacy Exception

For prosperity, versions of Torrust BitTorrent Tracker Client that are older than five years are automatically granted the [MIT-0][MIT_0] license in addition to the existing [LGPL-3.0-only][LGPL_3_0] license.

[LGPL_3_0]: ./LICENSE
[MIT_0]: ./docs/licenses/LICENSE-MIT_0
[FSF]: https://www.fsf.org/
14 changes: 14 additions & 0 deletions packages/tracker-api-client/docs/licenses/LICENSE-MIT_0
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
MIT No Attribution

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.

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 NONINFRINGEMENT. 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.
57 changes: 57 additions & 0 deletions packages/tracker-api-client/src/common/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
pub type ReqwestQuery = Vec<ReqwestQueryParam>;
pub type ReqwestQueryParam = (String, String);

/// URL Query component
#[derive(Default, Debug)]
pub struct Query {
params: Vec<QueryParam>,
}

impl Query {
#[must_use]
pub fn empty() -> Self {
Self { params: vec![] }
}

#[must_use]
pub fn params(params: Vec<QueryParam>) -> Self {
Self { params }
}

pub fn add_param(&mut self, param: QueryParam) {
self.params.push(param);
}
}

impl From<Query> for ReqwestQuery {
fn from(url_search_params: Query) -> Self {
url_search_params
.params
.iter()
.map(|param| ReqwestQueryParam::from((*param).clone()))
.collect()
}
}

/// URL query param
#[derive(Clone, Debug)]
pub struct QueryParam {
name: String,
value: String,
}

impl QueryParam {
#[must_use]
pub fn new(name: &str, value: &str) -> Self {
Self {
name: name.to_string(),
value: value.to_string(),
}
}
}

impl From<QueryParam> for ReqwestQueryParam {
fn from(param: QueryParam) -> Self {
(param.name, param.value)
}
}
1 change: 1 addition & 0 deletions packages/tracker-api-client/src/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod http;
154 changes: 154 additions & 0 deletions packages/tracker-api-client/src/connection_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
use std::str::FromStr;

use thiserror::Error;
use url::Url;

#[derive(Clone)]
pub struct ConnectionInfo {
pub origin: Origin,
pub api_token: Option<String>,
}

impl ConnectionInfo {
#[must_use]
pub fn authenticated(origin: Origin, api_token: &str) -> Self {
Self {
origin,
api_token: Some(api_token.to_string()),
}
}

#[must_use]
pub fn anonymous(origin: Origin) -> Self {
Self { origin, api_token: None }
}
}

/// Represents the origin of a HTTP request.
///
/// The format of the origin is a URL, but only the scheme, host, and port are used.
///
/// Pattern: `scheme://host:port/`
#[derive(Debug, Clone)]
pub struct Origin {
url: Url,
}

#[derive(Debug, Error)]
pub enum OriginError {
#[error("Invalid URL: {0}")]
InvalidUrl(#[from] url::ParseError),

#[error("URL is missing scheme or host")]
InvalidOrigin,

#[error("Invalid URL scheme, only http and https are supported")]
InvalidScheme,
}

impl FromStr for Origin {
type Err = OriginError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut url = Url::parse(s).map_err(OriginError::InvalidUrl)?;

// Ensure the URL has a scheme and host
if url.scheme().is_empty() || url.host().is_none() {
return Err(OriginError::InvalidOrigin);
}

if url.scheme() != "http" && url.scheme() != "https" {
return Err(OriginError::InvalidScheme);
}

// Retain only the origin components
url.set_path("/");
url.set_query(None);
url.set_fragment(None);

Ok(Origin { url })
}
}

impl std::fmt::Display for Origin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.url)
}
}

impl Origin {
/// # Errors
///
/// Will return an error if the string is not a valid URL containing a
/// scheme and host.
pub fn new(s: &str) -> Result<Self, OriginError> {
s.parse()
}

#[must_use]
pub fn url(&self) -> &Url {
&self.url
}
}

#[cfg(test)]
mod tests {
mod origin {
use crate::connection_info::Origin;

#[test]
fn should_be_parsed_from_a_string_representing_a_url() {
let origin = Origin::new("https://example.com:8080/path?query#fragment").unwrap();

assert_eq!(origin.to_string(), "https://example.com:8080/");
}

mod when_parsing_from_url_string {
use crate::connection_info::Origin;

#[test]
fn should_ignore_default_ports() {
let origin = Origin::new("http://example.com:80").unwrap(); // DevSkim: ignore DS137138
assert_eq!(origin.to_string(), "http://example.com/"); // DevSkim: ignore DS137138

let origin = Origin::new("https://example.com:443").unwrap();
assert_eq!(origin.to_string(), "https://example.com/");
}

#[test]
fn should_add_the_slash_after_the_host() {
let origin = Origin::new("https://example.com:1212").unwrap();

assert_eq!(origin.to_string(), "https://example.com:1212/");
}

#[test]
fn should_remove_extra_path_and_query_parameters() {
let origin = Origin::new("https://example.com:1212/path/to/resource?query=1#fragment").unwrap();

assert_eq!(origin.to_string(), "https://example.com:1212/");
}

#[test]
fn should_fail_when_the_scheme_is_missing() {
let result = Origin::new("example.com");

assert!(result.is_err());
}

#[test]
fn should_fail_when_the_scheme_is_not_supported() {
let result = Origin::new("udp://example.com");

assert!(result.is_err());
}

#[test]
fn should_fail_when_the_host_is_missing() {
let result = Origin::new("http://");

assert!(result.is_err());
}
}
}
}
3 changes: 3 additions & 0 deletions packages/tracker-api-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod common;
pub mod connection_info;
pub mod v1;
Loading
Loading