From 52a8c488254c55318f7be83a3be67c533f7d0272 Mon Sep 17 00:00:00 2001 From: askiiart Date: Sun, 24 Nov 2024 23:09:31 -0600 Subject: [PATCH] - update function signatures - add tests (doesn't verify anything, but useful to manually test something/run bits) - improve documentation --- Cargo.lock | 12 +---- Cargo.toml | 3 +- src/api.rs | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++-- src/data.rs | 98 ++++++++++++++++++++------------------ src/lib.rs | 6 +-- 5 files changed, 189 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5861e8c..e4ade1d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1255,7 +1255,8 @@ name = "torznab-toolkit" version = "0.1.0" dependencies = [ "rocket", - "xml", + "serde", + "xml-rs", ] [[package]] @@ -1568,15 +1569,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "xml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede1c99c55b4b3ad0349018ef0eccbe954ce9c342334410707ee87177fcf2ab4" -dependencies = [ - "xml-rs", -] - [[package]] name = "xml-rs" version = "0.8.23" diff --git a/Cargo.toml b/Cargo.toml index 597e3a7..4dbfc86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" [dependencies] rocket = "0.5.1" -xml = "0.8.20" +serde = { version = "1.0.215", features = ["derive"] } +xml-rs = "0.8.23" diff --git a/src/api.rs b/src/api.rs index fae11ff..fec5f17 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,7 +1,134 @@ +//! Contains the actual Torznab API +use std::io::stdout; + use crate::data::*; use rocket::get; +use rocket::http::Status; +use rocket::response::status; +use xml::writer::{EmitterConfig, XmlEvent}; -/// A struct that holds configuration for torznab-toolkit -/// A search function (/api?t=search) and capabilities (/api?t=caps - struct Caps) required -/// Everything else is optional -pub static mut config: Option = None; + +/// Holds the config for torznab-toolkit. +/// +/// A search function (`/api?t=search`) and capabilities (`/api?t=caps` - `Caps`) are required, everything else is optional. +/// +///
It's required to be set to something, which is why it's an Option set to None. +/// +/// However, this is NOT optional, and attempting to do anything with CONFIG not set will return an `Err`.
+pub static mut CONFIG: Option = None; + +/// Capabilities API endpoint (`/api?t=caps`) +// FIXME: VERY incomplete +// TODO: Finish it (duh) and add optional apikey +#[get("/api?t=caps")] +pub(crate) fn caps() -> status::Custom { + unsafe { + if CONFIG == None { + return status::Custom( + Status::InternalServerError, + "500 Internal server error: Config not specified".to_string(), + ); + } + } + + let output = stdout(); + let mut writer = EmitterConfig::new().create_writer(output); + + writer.write(XmlEvent::start_element("caps")).unwrap(); + writer.write(XmlEvent::start_element("server")).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + writer.write(XmlEvent::start_element("caps")).unwrap(); + writer.write(XmlEvent::end_element()).unwrap(); + return status::Custom(Status::Ok, stringify!(writer).to_string()); +} + +#[cfg(test)] +mod tests { + use super::*; + + fn dummy_search_func(a: String, b: Vec) -> Result { + return Ok("hi".to_string()); + } + + fn dummy_auth_func(a: String) -> Result { + return Ok(true); + } + + fn create_config() -> Config { + let mut searching = Vec::new(); + searching.push(SearchInfo { + search_type: "search".to_string(), + available: true, + supported_params: vec!["id".to_string()], + }); + + let mut subcategories = Vec::new(); + subcategories.push(Subcategory { + id: "a".to_string(), + name: "b".to_string(), + }); + + let mut categories = Vec::new(); + categories.push(Category { + id: "a".to_string(), + name: "b".to_string(), + subcategories: subcategories, + }); + + let mut genres = Vec::new(); + genres.push(Genre { + id: "a".to_string(), + category_id: "b".to_string(), + name: "c".to_string(), + }); + + let mut tags = Vec::new(); + tags.push(Tag { + id: "a".to_string(), + category_id: "b".to_string(), + name: "c".to_string(), + }); + + return Config { + search: dummy_search_func, + auth: Some(dummy_auth_func), + caps: Caps { + server_info: ServerInfo { + title: Some("Test Torznab server".to_string()), + email: Some("test@example.com".to_string()), + image: None, + version: Some("1.0".to_string()), + }, + limits: Limits { + max: 100, + default: 20, + }, + searching: searching, + categories: categories, + genres: Some(genres), + tags: Some(tags), + }, + book: None, + movie: None, + music: None, + tvsearch: None, + }; + } + + #[test] + fn test_with_config() { + unsafe { + CONFIG = Some(create_config()); + println!("{:?}", CONFIG); + } + caps(); + } + + #[test] + fn test_empty_config() { + unsafe { + println!("{:?}", CONFIG); + } + caps(); + } +} diff --git a/src/data.rs b/src/data.rs index f001577..e145be2 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,23 +1,25 @@ -pub(crate) type AuthFunc = fn(String) -> Result; +//! Contains tons of structs used by the library +pub(crate) type AuthFunc = fn(String) -> Result; +// TODO: Figure out what the arguments should be for a search function and what it should return pub(crate) type SearchFunc = fn(String, Vec) -> Result; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// Specify the ServerInfo to be listed in for `/api?t=caps` +/// +/// These fields are just those listed in the example on [torznab.github.io](https://torznab.github.io), there's no actual specification for thse fields. +/// TODO: Update this to have customizable fields instead pub struct ServerInfo { - /// Specify the ServerInfo to be listed in for `/api?t=caps` - /// - /// These fields are just those listed in the example on [torznab.github.io](https://torznab.github.io), there's no actual specification for thse fields. - /// TODO: Update this to have customizable fields instead pub title: Option, pub email: Option, pub image: Option, pub version: Option, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// The maximum and defaults for the `limit` parameter in queries +/// `max` is the maximum number of results the program can return +/// `default` is the default number of results the program will return pub struct Limits { - /// The maximum and defaults for the `limit` parameter in queries - /// `max` is the maximum number of results the program can return - /// `default` is the default number of results the program will return /* I don't know why this would possibly need to be a u64, I can't imagine you'll be returning 18 quintillion results or whatever In fact, I *really* hope you aren't - if you are, you're doing something extremely wrong @@ -27,68 +29,72 @@ pub struct Limits { pub default: u64, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// A struct holding the info for a type of search +/// - `search_type` must be `search`, `tv-search`, `movie-search`, `audio-search`, or `book-search` +/// - `available` pub struct SearchInfo { - /// A struct holding the info for a type of search - /// - `search_type` must be `search`, `tv-search`, `movie-search`, `audio-search`, or `book-search` - /// - `available` pub search_type: String, pub available: bool, pub supported_params: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// Contains subcategories, for use in `Category` pub struct Subcategory { pub id: String, pub name: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// Contains a category, for use in `Caps` and searches as a query parameter pub struct Category { pub id: String, pub name: String, pub subcategories: Vec, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// Contains a genre, for use in `Caps` and searches as a query parameter pub struct Genre { pub id: String, pub category_id: String, pub name: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// Contains a tag, for use in `Caps` and searches as a query parameter pub struct Tag { pub id: String, pub category_id: String, pub name: String, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// Holds the configuration for the capabilities of the Torznab server +/// +/// - server_info: `ServerInfo` +/// - see: `ServerInfo` docs +/// - limits: `Limits` +/// - specifies the max and default items listed when searching +/// - see: `Limits` docs +/// - searching: `Vec` +/// - specifies the capabilities of each search mode +/// - see: `SearchInfo` docs +/// - categories: `Vec` +/// - lists known categories +/// - see: `Category` docs +/// - genres: `Option>` +/// - lists known genres, optional +/// - see: `Genre` docs +/// +///
Note that this library might not support all the capabilities listed in yet, so check the README before listing capabilities, or just accept that unsupported capabilities will return error 404. +/// +/// It's recommended to add any capabilities you want, and set `available` to `false` in the `Caps` struct for any currently unsupported search types.
+/// +/// +/// TODO: Add a way to partially(?) generate automatically from the Config pub struct Caps { - /// Holds the configuration for the capabilities of the Torznab server - /// - /// - server_info: `ServerInfo` - /// - see: `ServerInfo` docs - /// - limits: `Limits` - /// - specifies the max and default items listed when searching - /// - see: `Limits` docs - /// - searching: `Vec` - /// - specifies the capabilities of each search mode - /// - see: `SearchInfo` docs - /// - categories: `Vec` - /// - lists known categories - /// - see: `Category` docs - /// - genres: `Option>` - /// - lists known genres, optional - /// - see: `Genre` docs - /// - ///
Note that this library might not support all the capabilities listed in yet, so check the README before listing capabilities, or just accept that unsupported capabilities will return error 404. - /// - /// It's recommended to add any capabilities you want, and set `available` to `false` in the `Caps` struct for any currently unsupported search types.
- /// - /// - /// TODO: Add a way to partially(?) generate automatically from the Config pub server_info: ServerInfo, pub limits: Limits, pub searching: Vec, @@ -97,11 +103,11 @@ pub struct Caps { pub tags: Option>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] +/// A struct that holds configuration for torznab-toolkit +/// A search function (/api?t=search) and capabilities (/api?t=caps - struct Caps) required +/// Everything else is optional pub struct Config { - /// A struct that holds configuration for torznab-toolkit - /// A search function (/api?t=search) and capabilities (/api?t=caps - struct Caps) required - /// Everything else is optional pub search: SearchFunc, // NOTE: This is NOT optional, pub auth: Option, pub caps: Caps, diff --git a/src/lib.rs b/src/lib.rs index 99fbe4e..9e08eec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,10 +23,8 @@ pub mod data; use rocket::{self}; +/// Runs the server pub fn run(conf: data::Config, caps: data::Caps) -> Result { - /// Runs the server - //rocket::build() - // .mount("/", rocket::routes![conf.caps]) - // .launch(); + rocket::build().mount("/", rocket::routes![]).launch(); return Ok(true); }