diff --git a/Cargo.toml b/Cargo.toml index 0a47e71d..4ef710aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ categories = ["network-programming", "web-programming"] exclude.workspace = true [workspace.package] -version = "21.10.0" +version = "21.11.0" readme = "README.md" edition = "2024" authors = ["root@ltpp.vip"] @@ -25,15 +25,15 @@ resolver = "2" members = ["compress", "constant", "type", "request", "core", "macros", "cli", "plugin/websocket"] [workspace.dependencies] -hyperlane = { path = ".", version = "21.10.0" } -http-type = { path = "type", version = "21.10.0" } -hyperlane-cli = { path = "cli", version = "21.10.0" } -hyperlane-core = { path = "core", version = "21.10.0" } -http-request = { path = "request", version = "21.10.0" } -http-compress = { path = "compress", version = "21.10.0" } -http-constant = { path = "constant", version = "21.10.0" } -hyperlane-macros = { path = "macros", version = "21.10.0" } -hyperlane-plugin-websocket = { path = "plugin/websocket", version = "21.10.0" } +hyperlane = { path = ".", version = "21.11.0" } +http-type = { path = "type", version = "21.11.0" } +hyperlane-cli = { path = "cli", version = "21.11.0" } +hyperlane-core = { path = "core", version = "21.11.0" } +http-request = { path = "request", version = "21.11.0" } +http-compress = { path = "compress", version = "21.11.0" } +http-constant = { path = "constant", version = "21.11.0" } +hyperlane-macros = { path = "macros", version = "21.11.0" } +hyperlane-plugin-websocket = { path = "plugin/websocket", version = "21.11.0" } hex = "0.4.3" md5 = "0.8.1" diff --git a/cli/src/config/const.rs b/cli/src/config/const.rs new file mode 100644 index 00000000..2e279dad --- /dev/null +++ b/cli/src/config/const.rs @@ -0,0 +1,11 @@ +/// Long form of the help flag. +pub(crate) const FLAG_LONG_HELP: &str = "--help"; + +/// Long form of the version flag. +pub(crate) const FLAG_LONG_VERSION: &str = "--version"; + +/// Sub-command that starts the file watcher. +pub(crate) const COMMAND_WATCH: &str = "watch"; + +/// Sub-command that scaffolds a component from a template. +pub(crate) const COMMAND_TEMPLATE: &str = "template"; diff --git a/cli/src/config/fn.rs b/cli/src/config/fn.rs index 0bf5ddf6..a7b28544 100644 --- a/cli/src/config/fn.rs +++ b/cli/src/config/fn.rs @@ -16,13 +16,13 @@ pub fn parse_args() -> Args { while i < raw_args.len() { let arg: &str = raw_args[i].as_str(); match arg { - "-h" | "--help" => { + "-h" | FLAG_LONG_HELP => { command = CommandType::Help; } - "-v" | "--version" => { + "-v" | FLAG_LONG_VERSION => { command = CommandType::Version; } - "watch" if (command == CommandType::Help || command == CommandType::Version) => { + COMMAND_WATCH if (command == CommandType::Help || command == CommandType::Version) => { command = CommandType::Watch; } "new" if (command == CommandType::Help || command == CommandType::Version) => { @@ -37,7 +37,9 @@ pub fn parse_args() -> Args { i -= 1; } } - "template" if (command == CommandType::Help || command == CommandType::Version) => { + COMMAND_TEMPLATE + if (command == CommandType::Help || command == CommandType::Version) => + { command = CommandType::Template; i += 1; if i < raw_args.len() diff --git a/cli/src/config/mod.rs b/cli/src/config/mod.rs index 7a92debb..a1e8e00d 100644 --- a/cli/src/config/mod.rs +++ b/cli/src/config/mod.rs @@ -1,6 +1,9 @@ +mod r#const; mod r#fn; mod r#struct; pub use {r#fn::*, r#struct::*}; +pub(crate) use r#const::*; + use super::*; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index dcb3d106..b9d6d9fb 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -14,13 +14,15 @@ pub use {command::*, config::*, help::*, logger::*, new::*, template::*, version pub(crate) use std::{ env::args, + fmt::Arguments, io, path::{Path, PathBuf}, - process::Stdio, + process::{Output, Stdio}, str::FromStr, }; pub(crate) use { + log::SetLoggerError, lombok_macros::*, notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher, recommended_watcher}, std::ffi::OsStr, diff --git a/cli/src/logger/impl.rs b/cli/src/logger/impl.rs index bf334994..45aa03f2 100644 --- a/cli/src/logger/impl.rs +++ b/cli/src/logger/impl.rs @@ -26,7 +26,7 @@ impl log::Log for Logger { } let now_time: String = color_output::time(); let level: log::Level = record.level(); - let args: &std::fmt::Arguments<'_> = record.args(); + let args: &Arguments<'_> = record.args(); let file: Option<&str> = record.file(); let module_path: Option<&str> = record.module_path(); let target: &str = record.target(); @@ -88,7 +88,7 @@ impl Logger { /// /// # Arguments /// - /// - `LevelFilter` - The maximum log level to enable. + /// - `log::LevelFilter` - The maximum log level to enable. pub fn init(level_filter: log::LevelFilter) { let _: Result<(), SetLoggerError> = log::set_logger(&LOGGER); log::set_max_level(level_filter); diff --git a/cli/src/logger/mod.rs b/cli/src/logger/mod.rs index 3ac4eaa7..1e551f85 100644 --- a/cli/src/logger/mod.rs +++ b/cli/src/logger/mod.rs @@ -8,6 +8,4 @@ pub use {::log, color_output::*}; pub(crate) use {r#const::*, r#static::*}; -pub(crate) use log::SetLoggerError; - use super::*; diff --git a/cli/src/main.rs b/cli/src/main.rs index 6dc43c8c..d0ebe694 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,6 +4,7 @@ use hyperlane_cli::*; use std::process::exit; +/// Parses the command line arguments and dispatches the selected sub-command. #[tokio::main] async fn main() { Logger::init(log::LevelFilter::Info); diff --git a/cli/src/new/const.rs b/cli/src/new/const.rs new file mode 100644 index 00000000..fbd013a8 --- /dev/null +++ b/cli/src/new/const.rs @@ -0,0 +1,20 @@ +/// Message reported when the project name is empty. +pub(crate) const ERROR_PROJECT_NAME_EMPTY: &str = "Project name cannot be empty"; + +/// Message reported when the project name holds a path separator or a colon. +pub(crate) const ERROR_PROJECT_NAME_INVALID_CHARACTERS: &str = + "Project name contains invalid characters"; + +/// Message reported when the project name starts with a dot or a dash. +pub(crate) const ERROR_PROJECT_NAME_INVALID_PREFIX: &str = + "Project name cannot start with '.' or '-'"; + +/// Repository cloned by the `new` command when no template is configured. +pub(crate) const DEFAULT_TEMPLATE_URL: &str = + "https://github.com/hyperlane-dev/hyperlane-quick-start"; + +/// Flag used to probe whether git is installed. +pub(crate) const GIT_FLAG_VERSION: &str = "--version"; + +/// Git sub-command that clones the template repository. +pub(crate) const GIT_SUBCOMMAND_CLONE: &str = "clone"; diff --git a/cli/src/new/fn.rs b/cli/src/new/fn.rs index cba88156..47e4068f 100644 --- a/cli/src/new/fn.rs +++ b/cli/src/new/fn.rs @@ -11,18 +11,16 @@ use super::*; /// - `Result<(), NewError>` - Ok if valid, error otherwise fn validate_project_name(name: &str) -> Result<(), NewError> { if name.is_empty() { - return Err(NewError::InvalidName( - "Project name cannot be empty".to_string(), - )); + return Err(NewError::InvalidName(ERROR_PROJECT_NAME_EMPTY.to_string())); } if name.contains('/') || name.contains('\\') || name.contains(':') { return Err(NewError::InvalidName( - "Project name contains invalid characters".to_string(), + ERROR_PROJECT_NAME_INVALID_CHARACTERS.to_string(), )); } if name.starts_with('.') || name.starts_with('-') { return Err(NewError::InvalidName( - "Project name cannot start with '.' or '-'".to_string(), + ERROR_PROJECT_NAME_INVALID_PREFIX.to_string(), )); } Ok(()) @@ -34,13 +32,13 @@ fn validate_project_name(name: &str) -> Result<(), NewError> { /// /// - `Result<(), NewError>` - Ok if git is available, error otherwise async fn check_git_available() -> Result<(), NewError> { - let output: std::process::Output = Command::new("git") - .arg("--version") + let output: Output = Command::new("git") + .arg(GIT_FLAG_VERSION) .stdout(Stdio::null()) .stderr(Stdio::null()) .output() .await - .map_err(|_: std::io::Error| NewError::GitNotFound)?; + .map_err(|_: io::Error| NewError::GitNotFound)?; if output.status.success() { Ok(()) } else { @@ -62,8 +60,8 @@ async fn git_clone(config: &NewProjectConfig) -> Result<(), NewError> { if project_path.exists() { return Err(NewError::ProjectExists(config.project_name.clone())); } - let output: std::process::Output = Command::new("git") - .arg("clone") + let output: Output = Command::new("git") + .arg(GIT_SUBCOMMAND_CLONE) .arg(&config.template_url) .arg(&config.project_name) .stdout(Stdio::piped()) diff --git a/cli/src/new/impl.rs b/cli/src/new/impl.rs index 48d85740..05fe81bc 100644 --- a/cli/src/new/impl.rs +++ b/cli/src/new/impl.rs @@ -13,7 +13,7 @@ impl NewProjectConfig { pub fn new(project_name: String) -> Self { Self { project_name, - template_url: "https://github.com/hyperlane-dev/hyperlane-quick-start".to_string(), + template_url: DEFAULT_TEMPLATE_URL.to_string(), } } } diff --git a/cli/src/new/mod.rs b/cli/src/new/mod.rs index 67ac4f49..9660f1ef 100644 --- a/cli/src/new/mod.rs +++ b/cli/src/new/mod.rs @@ -1,3 +1,4 @@ +mod r#const; mod r#enum; mod r#fn; mod r#impl; @@ -5,4 +6,6 @@ mod r#struct; pub use {r#enum::*, r#fn::*, r#struct::*}; +pub(crate) use r#const::*; + use super::*; diff --git a/cli/src/template/const.rs b/cli/src/template/const.rs new file mode 100644 index 00000000..9d6a29b8 --- /dev/null +++ b/cli/src/template/const.rs @@ -0,0 +1,89 @@ +/// Lowercase name of the `Controller` template type and its directory. +pub(crate) const TEMPLATE_TYPE_CONTROLLER: &str = "controller"; + +/// Lowercase name of the `Domain` template type and its directory. +pub(crate) const TEMPLATE_TYPE_DOMAIN: &str = "domain"; + +/// Lowercase name of the `Exception` template type and its directory. +pub(crate) const TEMPLATE_TYPE_EXCEPTION: &str = "exception"; + +/// Lowercase name of the `Mapper` template type and its directory. +pub(crate) const TEMPLATE_TYPE_MAPPER: &str = "mapper"; + +/// Lowercase name of the `Model` template type and its directory. +pub(crate) const TEMPLATE_TYPE_MODEL: &str = "model"; + +/// Lowercase name of the `Repository` template type and its directory. +pub(crate) const TEMPLATE_TYPE_REPOSITORY: &str = "repository"; + +/// Lowercase name of the `Service` template type and its directory. +pub(crate) const TEMPLATE_TYPE_SERVICE: &str = "service"; + +/// Lowercase name of the `Utils` template type and its directory. +pub(crate) const TEMPLATE_TYPE_UTILS: &str = "utils"; + +/// Lowercase name of the `View` template type and its directory. +pub(crate) const TEMPLATE_TYPE_VIEW: &str = "view"; + +/// Lowercase name of the `Application` model sub type and its directory. +pub(crate) const MODEL_SUB_TYPE_APPLICATION: &str = "application"; + +/// Lowercase name of the `Request` model sub type and its directory. +pub(crate) const MODEL_SUB_TYPE_REQUEST: &str = "request"; + +/// Lowercase name of the `Response` model sub type and its directory. +pub(crate) const MODEL_SUB_TYPE_RESPONSE: &str = "response"; + +/// Directory that holds every generated component tree. +pub(crate) const TEMPLATE_CONFIG_BASE_DIRECTORY: &str = "./application"; + +/// Keyword file name of the `const` module inside a generated directory. +pub(crate) const MODULE_NAME_CONST: &str = "const"; + +/// Keyword file name of the `enum` module inside a generated directory. +pub(crate) const MODULE_NAME_ENUM: &str = "enum"; + +/// Keyword file name of the `fn` module inside a generated directory. +pub(crate) const MODULE_NAME_FN: &str = "fn"; + +/// Keyword file name of the `impl` module inside a generated directory. +pub(crate) const MODULE_NAME_IMPL: &str = "impl"; + +/// Keyword file name of the `static` module inside a generated directory. +pub(crate) const MODULE_NAME_STATIC: &str = "static"; + +/// Keyword file name of the `struct` module inside a generated directory. +pub(crate) const MODULE_NAME_STRUCT: &str = "struct"; + +/// File name of the generated module declaration file. +pub(crate) const FILE_NAME_MOD_RS: &str = "mod.rs"; + +/// File name of the generated `const` file. +pub(crate) const FILE_NAME_CONST_RS: &str = "const.rs"; + +/// File name of the generated `enum` file. +pub(crate) const FILE_NAME_ENUM_RS: &str = "enum.rs"; + +/// File name of the generated `fn` file. +pub(crate) const FILE_NAME_FN_RS: &str = "fn.rs"; + +/// File name of the generated `impl` file. +pub(crate) const FILE_NAME_IMPL_RS: &str = "impl.rs"; + +/// File name of the generated `static` file. +pub(crate) const FILE_NAME_STATIC_RS: &str = "static.rs"; + +/// File name of the generated `struct` file. +pub(crate) const FILE_NAME_STRUCT_RS: &str = "struct.rs"; + +/// Opening delimiter of the aggregated `pub use` line in a generated `mod.rs`. +pub(crate) const PUB_USE_PREFIX: &str = "pub use {"; + +/// First line written into every generated keyword file. +pub(crate) const KEYWORD_FILE_HEADER: &str = "use super::*;\n"; + +/// Message reported when a model template is requested without a sub type. +pub(crate) const ERROR_MISSING_MODEL_SUB_TYPE: &str = "Missing model subtype"; + +/// Program used to format the freshly generated template code. +pub(crate) const CARGO_FMT_PROGRAM: &str = "cargo"; diff --git a/cli/src/template/fn.rs b/cli/src/template/fn.rs index f05491b9..ffaf7bca 100644 --- a/cli/src/template/fn.rs +++ b/cli/src/template/fn.rs @@ -11,15 +11,15 @@ use super::*; /// - `String` - Directory name fn get_directory_name(template_type: &TemplateType) -> String { match template_type { - TemplateType::Controller => "controller".to_string(), - TemplateType::Domain => "domain".to_string(), - TemplateType::Exception => "exception".to_string(), - TemplateType::Mapper => "mapper".to_string(), - TemplateType::Model => "model".to_string(), - TemplateType::Repository => "repository".to_string(), - TemplateType::Service => "service".to_string(), - TemplateType::Utils => "utils".to_string(), - TemplateType::View => "view".to_string(), + TemplateType::Controller => TEMPLATE_TYPE_CONTROLLER.to_string(), + TemplateType::Domain => TEMPLATE_TYPE_DOMAIN.to_string(), + TemplateType::Exception => TEMPLATE_TYPE_EXCEPTION.to_string(), + TemplateType::Mapper => TEMPLATE_TYPE_MAPPER.to_string(), + TemplateType::Model => TEMPLATE_TYPE_MODEL.to_string(), + TemplateType::Repository => TEMPLATE_TYPE_REPOSITORY.to_string(), + TemplateType::Service => TEMPLATE_TYPE_SERVICE.to_string(), + TemplateType::Utils => TEMPLATE_TYPE_UTILS.to_string(), + TemplateType::View => TEMPLATE_TYPE_VIEW.to_string(), } } @@ -34,9 +34,9 @@ fn get_directory_name(template_type: &TemplateType) -> String { /// - `String` - Directory name fn get_model_sub_type_name(sub_type: &ModelSubType) -> String { match sub_type { - ModelSubType::Application => "application".to_string(), - ModelSubType::Request => "request".to_string(), - ModelSubType::Response => "response".to_string(), + ModelSubType::Application => MODEL_SUB_TYPE_APPLICATION.to_string(), + ModelSubType::Request => MODEL_SUB_TYPE_REQUEST.to_string(), + ModelSubType::Response => MODEL_SUB_TYPE_RESPONSE.to_string(), } } @@ -89,21 +89,21 @@ async fn write_mod_rs(path: &Path, modules: &[&str]) -> Result<(), TemplateError } else { format!("r#{module}") }; - if raw_name == "const" || raw_name == "static" { + if raw_name == MODULE_NAME_CONST || raw_name == MODULE_NAME_STATIC { pub_use_parts.push(mod_name); - } else if raw_name == "enum" || raw_name == "fn" { + } else if raw_name == MODULE_NAME_ENUM || raw_name == MODULE_NAME_FN { pub_use_parts.push(format!("{mod_name}::*")); - } else if raw_name == "struct" { + } else if raw_name == MODULE_NAME_STRUCT { pub_use_parts.push(mod_name); } } if !pub_use_parts.is_empty() { - content.push_str("pub use {"); + content.push_str(PUB_USE_PREFIX); content.push_str(&pub_use_parts.join(", ")); content.push_str("};\n"); } content.push('\n'); - content.push_str("use super::*;\n"); + content.push_str(KEYWORD_FILE_HEADER); write(path, content).await?; Ok(()) } @@ -137,14 +137,18 @@ async fn create_controller_template( _component_name: &str, ) -> Result<(), TemplateError> { ensure_directory(target_dir).await?; - let mod_rs: PathBuf = target_dir.join("mod.rs"); - write_mod_rs(&mod_rs, &["fn", "impl", "struct"]).await?; - let fn_rs: PathBuf = target_dir.join("fn.rs"); - write(&fn_rs, "use super::*;\n").await?; - let impl_rs: PathBuf = target_dir.join("impl.rs"); - write(&impl_rs, "use super::*;\n").await?; - let struct_rs: PathBuf = target_dir.join("struct.rs"); - write(&struct_rs, "use super::*;\n").await?; + let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS); + write_mod_rs( + &mod_rs, + &[MODULE_NAME_FN, MODULE_NAME_IMPL, MODULE_NAME_STRUCT], + ) + .await?; + let fn_rs: PathBuf = target_dir.join(FILE_NAME_FN_RS); + write(&fn_rs, KEYWORD_FILE_HEADER).await?; + let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS); + write(&impl_rs, KEYWORD_FILE_HEADER).await?; + let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS); + write(&struct_rs, KEYWORD_FILE_HEADER).await?; Ok(()) } @@ -163,14 +167,18 @@ async fn create_view_template( _component_name: &str, ) -> Result<(), TemplateError> { ensure_directory(target_dir).await?; - let mod_rs: PathBuf = target_dir.join("mod.rs"); - write_mod_rs(&mod_rs, &["fn", "impl", "struct"]).await?; - let fn_rs: PathBuf = target_dir.join("fn.rs"); - write(&fn_rs, "use super::*;\n").await?; - let impl_rs: PathBuf = target_dir.join("impl.rs"); - write(&impl_rs, "use super::*;\n").await?; - let struct_rs: PathBuf = target_dir.join("struct.rs"); - write(&struct_rs, "use super::*;\n").await?; + let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS); + write_mod_rs( + &mod_rs, + &[MODULE_NAME_FN, MODULE_NAME_IMPL, MODULE_NAME_STRUCT], + ) + .await?; + let fn_rs: PathBuf = target_dir.join(FILE_NAME_FN_RS); + write(&fn_rs, KEYWORD_FILE_HEADER).await?; + let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS); + write(&impl_rs, KEYWORD_FILE_HEADER).await?; + let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS); + write(&struct_rs, KEYWORD_FILE_HEADER).await?; Ok(()) } @@ -189,12 +197,12 @@ async fn create_service_template( _component_name: &str, ) -> Result<(), TemplateError> { ensure_directory(target_dir).await?; - let mod_rs: PathBuf = target_dir.join("mod.rs"); - write_mod_rs(&mod_rs, &["impl", "struct"]).await?; - let impl_rs: PathBuf = target_dir.join("impl.rs"); - write(&impl_rs, "use super::*;\n").await?; - let struct_rs: PathBuf = target_dir.join("struct.rs"); - write(&struct_rs, "use super::*;\n").await?; + let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS); + write_mod_rs(&mod_rs, &[MODULE_NAME_IMPL, MODULE_NAME_STRUCT]).await?; + let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS); + write(&impl_rs, KEYWORD_FILE_HEADER).await?; + let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS); + write(&struct_rs, KEYWORD_FILE_HEADER).await?; Ok(()) } @@ -213,12 +221,12 @@ async fn create_domain_template( _component_name: &str, ) -> Result<(), TemplateError> { ensure_directory(target_dir).await?; - let mod_rs: PathBuf = target_dir.join("mod.rs"); - write_mod_rs(&mod_rs, &["impl", "struct"]).await?; - let impl_rs: PathBuf = target_dir.join("impl.rs"); - write(&impl_rs, "use super::*;\n").await?; - let struct_rs: PathBuf = target_dir.join("struct.rs"); - write(&struct_rs, "use super::*;\n").await?; + let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS); + write_mod_rs(&mod_rs, &[MODULE_NAME_IMPL, MODULE_NAME_STRUCT]).await?; + let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS); + write(&impl_rs, KEYWORD_FILE_HEADER).await?; + let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS); + write(&struct_rs, KEYWORD_FILE_HEADER).await?; Ok(()) } @@ -237,24 +245,31 @@ async fn create_mapper_template( _component_name: &str, ) -> Result<(), TemplateError> { ensure_directory(target_dir).await?; - let mod_rs: PathBuf = target_dir.join("mod.rs"); + let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS); write_mod_rs( &mod_rs, - &["const", "enum", "fn", "impl", "static", "struct"], + &[ + MODULE_NAME_CONST, + MODULE_NAME_ENUM, + MODULE_NAME_FN, + MODULE_NAME_IMPL, + MODULE_NAME_STATIC, + MODULE_NAME_STRUCT, + ], ) .await?; - let const_rs: PathBuf = target_dir.join("const.rs"); - write(&const_rs, "use super::*;\n").await?; - let enum_rs: PathBuf = target_dir.join("enum.rs"); - write(&enum_rs, "use super::*;\n").await?; - let fn_rs: PathBuf = target_dir.join("fn.rs"); - write(&fn_rs, "use super::*;\n").await?; - let impl_rs: PathBuf = target_dir.join("impl.rs"); - write(&impl_rs, "use super::*;\n").await?; - let static_rs: PathBuf = target_dir.join("static.rs"); - write(&static_rs, "use super::*;\n").await?; - let struct_rs: PathBuf = target_dir.join("struct.rs"); - write(&struct_rs, "use super::*;\n").await?; + let const_rs: PathBuf = target_dir.join(FILE_NAME_CONST_RS); + write(&const_rs, KEYWORD_FILE_HEADER).await?; + let enum_rs: PathBuf = target_dir.join(FILE_NAME_ENUM_RS); + write(&enum_rs, KEYWORD_FILE_HEADER).await?; + let fn_rs: PathBuf = target_dir.join(FILE_NAME_FN_RS); + write(&fn_rs, KEYWORD_FILE_HEADER).await?; + let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS); + write(&impl_rs, KEYWORD_FILE_HEADER).await?; + let static_rs: PathBuf = target_dir.join(FILE_NAME_STATIC_RS); + write(&static_rs, KEYWORD_FILE_HEADER).await?; + let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS); + write(&struct_rs, KEYWORD_FILE_HEADER).await?; Ok(()) } @@ -273,10 +288,10 @@ async fn create_utils_template( _component_name: &str, ) -> Result<(), TemplateError> { ensure_directory(target_dir).await?; - let mod_rs: PathBuf = target_dir.join("mod.rs"); - write_mod_rs(&mod_rs, &["fn"]).await?; - let fn_rs: PathBuf = target_dir.join("fn.rs"); - write(&fn_rs, "use super::*;\n").await?; + let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS); + write_mod_rs(&mod_rs, &[MODULE_NAME_FN]).await?; + let fn_rs: PathBuf = target_dir.join(FILE_NAME_FN_RS); + write(&fn_rs, KEYWORD_FILE_HEADER).await?; Ok(()) } @@ -295,7 +310,7 @@ async fn create_exception_template( _component_name: &str, ) -> Result<(), TemplateError> { ensure_directory(target_dir).await?; - let mod_rs: PathBuf = target_dir.join("mod.rs"); + let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS); write_empty_mod_rs(&mod_rs).await?; Ok(()) } @@ -315,12 +330,12 @@ async fn create_repository_template( _component_name: &str, ) -> Result<(), TemplateError> { ensure_directory(target_dir).await?; - let mod_rs: PathBuf = target_dir.join("mod.rs"); - write_mod_rs(&mod_rs, &["impl", "struct"]).await?; - let impl_rs: PathBuf = target_dir.join("impl.rs"); - write(&impl_rs, "use super::*;\n").await?; - let struct_rs: PathBuf = target_dir.join("struct.rs"); - write(&struct_rs, "use super::*;\n").await?; + let mod_rs: PathBuf = target_dir.join(FILE_NAME_MOD_RS); + write_mod_rs(&mod_rs, &[MODULE_NAME_IMPL, MODULE_NAME_STRUCT]).await?; + let impl_rs: PathBuf = target_dir.join(FILE_NAME_IMPL_RS); + write(&impl_rs, KEYWORD_FILE_HEADER).await?; + let struct_rs: PathBuf = target_dir.join(FILE_NAME_STRUCT_RS); + write(&struct_rs, KEYWORD_FILE_HEADER).await?; Ok(()) } @@ -343,10 +358,10 @@ async fn create_model_template( let sub_type_name: String = get_model_sub_type_name(sub_type); let model_dir: PathBuf = target_dir.join(&sub_type_name); ensure_directory(&model_dir).await?; - let mod_rs: PathBuf = model_dir.join("mod.rs"); - write_mod_rs(&mod_rs, &["struct"]).await?; - let struct_rs: PathBuf = model_dir.join("struct.rs"); - write(&struct_rs, "use super::*;\n").await?; + let mod_rs: PathBuf = model_dir.join(FILE_NAME_MOD_RS); + write_mod_rs(&mod_rs, &[MODULE_NAME_STRUCT]).await?; + let struct_rs: PathBuf = model_dir.join(FILE_NAME_STRUCT_RS); + write(&struct_rs, KEYWORD_FILE_HEADER).await?; Ok(()) } @@ -354,9 +369,9 @@ async fn create_model_template( /// /// # Arguments /// -/// - `&TemplateType` - Type of template component +/// - `TemplateType` - Type of template component /// - `&str` - Name of the component -/// - `model_sub_type` - Optional model subtype +/// - `Option` - Optional model subtype for model components /// /// # Returns /// @@ -397,7 +412,7 @@ pub async fn execute_template( } TemplateType::Model => { let sub_type: ModelSubType = config.model_sub_type.ok_or_else(|| { - TemplateError::InvalidModelSubType("Missing model subtype".to_string()) + TemplateError::InvalidModelSubType(ERROR_MISSING_MODEL_SUB_TYPE.to_string()) })?; create_model_template(&target_dir, &config.component_name, &sub_type).await?; } @@ -422,8 +437,10 @@ pub async fn execute_template( /// /// - `Result<(), io::Error>` - Success or error async fn format_generated_path(path: &Path) -> Result<(), io::Error> { - let mut cmd: Command = Command::new("cargo"); - cmd.arg("fmt").arg("--").arg(path); + let mut cmd: Command = Command::new(CARGO_FMT_PROGRAM); + cmd.arg("fmt"); + cmd.arg("--"); + cmd.arg(path); cmd.stdout(Stdio::null()).stderr(Stdio::null()); cmd.status().await?; Ok(()) diff --git a/cli/src/template/impl.rs b/cli/src/template/impl.rs index 419a175d..90b0e711 100644 --- a/cli/src/template/impl.rs +++ b/cli/src/template/impl.rs @@ -3,17 +3,26 @@ use super::*; impl FromStr for TemplateType { type Err = TemplateError; + /// Parses a lowercase template type name. + /// + /// # Arguments + /// + /// - `&str` - The template type name to parse. + /// + /// # Returns + /// + /// - `Result` - The parsed template type or an error. fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { - "controller" => Ok(Self::Controller), - "domain" => Ok(Self::Domain), - "exception" => Ok(Self::Exception), - "mapper" => Ok(Self::Mapper), - "model" => Ok(Self::Model), - "repository" => Ok(Self::Repository), - "service" => Ok(Self::Service), - "utils" => Ok(Self::Utils), - "view" => Ok(Self::View), + TEMPLATE_TYPE_CONTROLLER => Ok(Self::Controller), + TEMPLATE_TYPE_DOMAIN => Ok(Self::Domain), + TEMPLATE_TYPE_EXCEPTION => Ok(Self::Exception), + TEMPLATE_TYPE_MAPPER => Ok(Self::Mapper), + TEMPLATE_TYPE_MODEL => Ok(Self::Model), + TEMPLATE_TYPE_REPOSITORY => Ok(Self::Repository), + TEMPLATE_TYPE_SERVICE => Ok(Self::Service), + TEMPLATE_TYPE_UTILS => Ok(Self::Utils), + TEMPLATE_TYPE_VIEW => Ok(Self::View), _ => Err(TemplateError::InvalidTemplateType(s.to_string())), } } @@ -40,7 +49,7 @@ impl TemplateConfig { template_type, component_name, model_sub_type, - base_directory: "./application".to_string(), + base_directory: TEMPLATE_CONFIG_BASE_DIRECTORY.to_string(), } } } @@ -48,11 +57,20 @@ impl TemplateConfig { impl FromStr for ModelSubType { type Err = TemplateError; + /// Parses a lowercase model sub type name. + /// + /// # Arguments + /// + /// - `&str` - The model sub type name to parse. + /// + /// # Returns + /// + /// - `Result` - The parsed model sub type or an error. fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { - "application" => Ok(Self::Application), - "request" => Ok(Self::Request), - "response" => Ok(Self::Response), + MODEL_SUB_TYPE_APPLICATION => Ok(Self::Application), + MODEL_SUB_TYPE_REQUEST => Ok(Self::Request), + MODEL_SUB_TYPE_RESPONSE => Ok(Self::Response), _ => Err(TemplateError::InvalidModelSubType(s.to_string())), } } diff --git a/cli/src/template/mod.rs b/cli/src/template/mod.rs index 67ac4f49..9660f1ef 100644 --- a/cli/src/template/mod.rs +++ b/cli/src/template/mod.rs @@ -1,3 +1,4 @@ +mod r#const; mod r#enum; mod r#fn; mod r#impl; @@ -5,4 +6,6 @@ mod r#struct; pub use {r#enum::*, r#fn::*, r#struct::*}; +pub(crate) use r#const::*; + use super::*; diff --git a/cli/src/watch/const.rs b/cli/src/watch/const.rs new file mode 100644 index 00000000..5f8a98d0 --- /dev/null +++ b/cli/src/watch/const.rs @@ -0,0 +1,6 @@ +/// Program re-run by the watch command on every source change. +pub(crate) const CARGO_RUN_PROGRAM: &str = "cargo"; + +/// Message reported when the watch command runs outside a crate. +pub(crate) const ERROR_SRC_DIRECTORY_NOT_FOUND: &str = + "src directory not found in current directory"; diff --git a/cli/src/watch/fn.rs b/cli/src/watch/fn.rs index 1da6bff4..1c2dd069 100644 --- a/cli/src/watch/fn.rs +++ b/cli/src/watch/fn.rs @@ -6,7 +6,7 @@ use super::*; /// /// - `Result<(), io::Error>` - Success or error async fn run_cargo_run() -> Result<(), io::Error> { - let output: std::process::Output = Command::new("cargo") + let output: Output = Command::new(CARGO_RUN_PROGRAM) .arg("run") .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -49,9 +49,7 @@ async fn run_cargo_run() -> Result<(), io::Error> { pub async fn execute_watch() -> Result<(), io::Error> { let src_path: PathBuf = PathBuf::from("src"); if !src_path.exists() { - return Err(io::Error::other( - "src directory not found in current directory", - )); + return Err(io::Error::other(ERROR_SRC_DIRECTORY_NOT_FOUND)); } run_cargo_run().await?; let (tx, mut rx): (Sender, Receiver) = channel(Event::new(EventKind::Any)); diff --git a/cli/src/watch/mod.rs b/cli/src/watch/mod.rs index 7087a15a..2c257cc0 100644 --- a/cli/src/watch/mod.rs +++ b/cli/src/watch/mod.rs @@ -1,5 +1,8 @@ +mod r#const; mod r#fn; pub use r#fn::*; +pub(crate) use r#const::*; + use super::*; diff --git a/cli/tests/command/fn.rs b/cli/tests/command/fn.rs new file mode 100644 index 00000000..b46595bc --- /dev/null +++ b/cli/tests/command/fn.rs @@ -0,0 +1,48 @@ +use super::*; + +#[test] +fn test_command_type_is_copy() { + let value: CommandType = CommandType::Template; + let copied: CommandType = value; + assert_eq!(value, copied); +} + +#[test] +fn test_command_type_variants_are_distinct() { + assert_ne!(CommandType::Watch, CommandType::New); + assert_ne!(CommandType::New, CommandType::Template); + assert_ne!(CommandType::Template, CommandType::Help); + assert_ne!(CommandType::Help, CommandType::Version); + assert_ne!(CommandType::Version, CommandType::Watch); +} + +#[test] +fn test_command_type_debug_names_variant() { + let watch: String = format!("{:?}", CommandType::Watch); + let version: String = format!("{:?}", CommandType::Version); + assert_eq!(watch, "Watch"); + assert_eq!(version, "Version"); +} + +#[test] +fn test_command_type_copy_preserves_value() { + let original: CommandType = CommandType::New; + let copied: CommandType = original; + assert_eq!(copied, CommandType::New); + assert_eq!(original, CommandType::New); +} + +#[test] +fn test_args_debug_lists_optional_fields() { + let args: Args = Args { + command: CommandType::Template, + project_name: Some("proj".to_string()), + template_type: Some(TemplateType::Model), + model_sub_type: Some(ModelSubType::Application), + component_name: Some("thing".to_string()), + }; + let debugged: String = format!("{args:?}"); + assert!(debugged.contains("proj")); + assert!(debugged.contains("thing")); + assert!(debugged.contains("Model")); +} diff --git a/cli/tests/command/mod.rs b/cli/tests/command/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/cli/tests/command/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/cli/tests/config/fn.rs b/cli/tests/config/fn.rs index bcba6a47..1ab958a6 100644 --- a/cli/tests/config/fn.rs +++ b/cli/tests/config/fn.rs @@ -1,5 +1,38 @@ use super::*; +#[test] +fn test_parse_args_defaults_to_help_without_command_tokens() { + let recognised: bool = args().any(|item: String| { + matches!( + item.as_str(), + "-h" | "--help" | "-v" | "--version" | "watch" | "new" | "template" + ) + }); + if recognised { + return; + } + let args: Args = parse_args(); + assert_eq!(args.command, CommandType::Help); + assert!(args.project_name.is_none()); + assert!(args.template_type.is_none()); + assert!(args.model_sub_type.is_none()); + assert!(args.component_name.is_none()); +} + +#[test] +fn test_parse_args_returns_populated_args() { + let args: Args = parse_args(); + let command: CommandType = args.command; + assert!(matches!( + command, + CommandType::Watch + | CommandType::New + | CommandType::Template + | CommandType::Help + | CommandType::Version + )); +} + #[test] fn test_args_default_values() { let args: Args = Args { diff --git a/cli/tests/help/fn.rs b/cli/tests/help/fn.rs new file mode 100644 index 00000000..6cb6263e --- /dev/null +++ b/cli/tests/help/fn.rs @@ -0,0 +1,12 @@ +use super::*; + +#[test] +fn test_print_help_runs_without_logger() { + print_help(); +} + +#[test] +fn test_print_help_is_idempotent() { + print_help(); + print_help(); +} diff --git a/cli/tests/help/mod.rs b/cli/tests/help/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/cli/tests/help/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/cli/tests/logger/fn.rs b/cli/tests/logger/fn.rs new file mode 100644 index 00000000..f16e0c83 --- /dev/null +++ b/cli/tests/logger/fn.rs @@ -0,0 +1,44 @@ +use super::*; + +#[test] +fn test_logger_unit_value_is_constructible() { + let logger: Logger = Logger; + let other: Logger = Logger; + log::Log::flush(&logger); + log::Log::flush(&other); +} + +#[test] +fn test_logger_flush_is_a_noop() { + let logger: Logger = Logger; + log::Log::flush(&logger); +} + +#[test] +fn test_logger_enabled_follows_max_level() { + log::set_max_level(log::LevelFilter::Info); + let logger: Logger = Logger; + let error_metadata: log::Metadata<'_> = log::Metadata::builder() + .level(log::Level::Error) + .target("hyperlane-cli") + .build(); + let trace_metadata: log::Metadata<'_> = log::Metadata::builder() + .level(log::Level::Trace) + .target("hyperlane-cli") + .build(); + assert!(log::Log::enabled(&logger, &error_metadata)); + assert!(!log::Log::enabled(&logger, &trace_metadata)); +} + +#[test] +fn test_logger_init_sets_max_level() { + Logger::init(log::LevelFilter::Warn); + assert_eq!(log::max_level(), log::LevelFilter::Warn); + log::set_max_level(log::LevelFilter::Info); +} + +#[test] +fn test_logger_new_constructs_instance() { + let logger: Logger = Logger::new(); + log::Log::flush(&logger); +} diff --git a/cli/tests/logger/mod.rs b/cli/tests/logger/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/cli/tests/logger/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/cli/tests/mod.rs b/cli/tests/mod.rs index 19045455..45e962ee 100644 --- a/cli/tests/mod.rs +++ b/cli/tests/mod.rs @@ -1,7 +1,11 @@ +mod command; mod config; +mod help; +mod logger; mod new; +mod template; mod version; use hyperlane_cli::*; -use std::io; +use std::{env::args, io}; diff --git a/cli/tests/new/fn.rs b/cli/tests/new/fn.rs index 266865f5..73f80d58 100644 --- a/cli/tests/new/fn.rs +++ b/cli/tests/new/fn.rs @@ -50,3 +50,68 @@ fn test_new_project_config_debug() { let debug_str: String = format!("{config:?}"); assert!(debug_str.contains("test")); } + +#[test] +fn test_new_project_config_fields_are_mutable() { + let mut config: NewProjectConfig = NewProjectConfig::new("first".to_string()); + config.project_name = "second".to_string(); + config.template_url = "https://example.test/repo".to_string(); + assert_eq!(config.project_name, "second"); + assert_eq!(config.template_url, "https://example.test/repo"); +} + +#[test] +fn test_new_project_config_new_keeps_given_name() { + let config: NewProjectConfig = NewProjectConfig::new(String::new()); + assert_eq!(config.project_name, ""); + assert!(config.template_url.starts_with("https://")); +} + +#[tokio::test] +async fn test_execute_new_rejects_empty_project_name() { + let result: Result<(), NewError> = execute_new("").await; + assert!(result.is_err()); + let error: NewError = result.unwrap_err(); + assert!(error.to_string().contains("Invalid project name")); + assert!(error.to_string().contains("empty")); +} + +#[tokio::test] +async fn test_execute_new_rejects_path_separators() { + let slash: Result<(), NewError> = execute_new("a/b").await; + assert!( + slash + .unwrap_err() + .to_string() + .contains("invalid characters") + ); + let backslash: Result<(), NewError> = execute_new("a\\b").await; + assert!( + backslash + .unwrap_err() + .to_string() + .contains("invalid characters") + ); + let colon: Result<(), NewError> = execute_new("a:b").await; + assert!( + colon + .unwrap_err() + .to_string() + .contains("invalid characters") + ); +} + +#[tokio::test] +async fn test_execute_new_rejects_dot_and_dash_prefix() { + let dot: Result<(), NewError> = execute_new(".hidden").await; + assert!(dot.unwrap_err().to_string().contains("cannot start")); + let dash: Result<(), NewError> = execute_new("-flag").await; + assert!(dash.unwrap_err().to_string().contains("cannot start")); +} + +#[tokio::test] +async fn test_execute_new_name_check_precedes_git_probe() { + let result: Result<(), NewError> = execute_new("..").await; + let error: NewError = result.unwrap_err(); + assert!(matches!(error, NewError::InvalidName(message) if message.contains("cannot start"))); +} diff --git a/cli/tests/template/fn.rs b/cli/tests/template/fn.rs new file mode 100644 index 00000000..4eec7a0b --- /dev/null +++ b/cli/tests/template/fn.rs @@ -0,0 +1,217 @@ +use super::*; + +#[test] +fn test_template_type_from_str_lowercase_names() { + assert_eq!( + "controller".parse::().ok(), + Some(TemplateType::Controller) + ); + assert_eq!( + "domain".parse::().ok(), + Some(TemplateType::Domain) + ); + assert_eq!( + "exception".parse::().ok(), + Some(TemplateType::Exception) + ); + assert_eq!( + "mapper".parse::().ok(), + Some(TemplateType::Mapper) + ); + assert_eq!( + "model".parse::().ok(), + Some(TemplateType::Model) + ); + assert_eq!( + "repository".parse::().ok(), + Some(TemplateType::Repository) + ); + assert_eq!( + "service".parse::().ok(), + Some(TemplateType::Service) + ); + assert_eq!( + "utils".parse::().ok(), + Some(TemplateType::Utils) + ); + assert_eq!( + "view".parse::().ok(), + Some(TemplateType::View) + ); +} + +#[test] +fn test_template_type_from_str_is_case_insensitive() { + assert_eq!( + "CONTROLLER".parse::().ok(), + Some(TemplateType::Controller) + ); + assert_eq!( + "Model".parse::().ok(), + Some(TemplateType::Model) + ); +} + +#[test] +fn test_template_type_from_str_rejects_unknown() { + let result: Result = "nope".parse::(); + assert!(result.is_err()); + let error: TemplateError = result.unwrap_err(); + assert!(error.to_string().contains("Invalid template type: nope")); +} + +#[test] +fn test_template_type_from_str_rejects_empty() { + let result: Result = "".parse::(); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .to_string() + .contains("Invalid template type") + ); +} + +#[test] +fn test_template_type_variants_are_distinct() { + assert_ne!(TemplateType::Controller, TemplateType::Model); + assert_ne!(TemplateType::Domain, TemplateType::Service); + assert_ne!(TemplateType::Mapper, TemplateType::Repository); + assert_ne!(TemplateType::Exception, TemplateType::Utils); + assert_ne!(TemplateType::View, TemplateType::Controller); +} + +#[test] +fn test_template_type_is_copy() { + let value: TemplateType = TemplateType::Domain; + let copied: TemplateType = value; + assert_eq!(value, copied); +} + +#[test] +fn test_model_sub_type_from_str_lowercase_names() { + assert_eq!( + "application".parse::().ok(), + Some(ModelSubType::Application) + ); + assert_eq!( + "request".parse::().ok(), + Some(ModelSubType::Request) + ); + assert_eq!( + "response".parse::().ok(), + Some(ModelSubType::Response) + ); +} + +#[test] +fn test_model_sub_type_from_str_is_case_insensitive() { + assert_eq!( + "Request".parse::().ok(), + Some(ModelSubType::Request) + ); + assert_eq!( + "RESPONSE".parse::().ok(), + Some(ModelSubType::Response) + ); +} + +#[test] +fn test_model_sub_type_from_str_rejects_template_names() { + let result: Result = "model".parse::(); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .to_string() + .contains("Invalid model subtype: model") + ); +} + +#[test] +fn test_model_sub_type_variants_are_distinct() { + assert_ne!(ModelSubType::Application, ModelSubType::Request); + assert_ne!(ModelSubType::Request, ModelSubType::Response); + assert_ne!(ModelSubType::Application, ModelSubType::Response); +} + +#[test] +fn test_template_config_new_defaults_base_directory() { + let config: TemplateConfig = TemplateConfig::new( + TemplateType::Controller, + "user".to_string(), + None::, + ); + assert_eq!(config.template_type, TemplateType::Controller); + assert_eq!(config.component_name, "user"); + assert!(config.model_sub_type.is_none()); + assert_eq!(config.base_directory, "./application"); +} + +#[test] +fn test_template_config_new_keeps_model_sub_type() { + let config: TemplateConfig = TemplateConfig::new( + TemplateType::Model, + "order".to_string(), + Some(ModelSubType::Request), + ); + assert_eq!(config.template_type, TemplateType::Model); + assert_eq!(config.model_sub_type, Some(ModelSubType::Request)); + assert_eq!(config.component_name, "order"); +} + +#[test] +fn test_template_config_fields_are_mutable() { + let mut config: TemplateConfig = TemplateConfig::new( + TemplateType::Utils, + "helper".to_string(), + None::, + ); + config.template_type = TemplateType::View; + config.component_name = "page".to_string(); + config.model_sub_type = Some(ModelSubType::Response); + config.base_directory = "./custom".to_string(); + assert_eq!(config.template_type, TemplateType::View); + assert_eq!(config.component_name, "page"); + assert_eq!(config.model_sub_type, Some(ModelSubType::Response)); + assert_eq!(config.base_directory, "./custom"); +} + +#[test] +fn test_template_config_clone_and_debug() { + let config: TemplateConfig = TemplateConfig::new( + TemplateType::Service, + "svc".to_string(), + None::, + ); + let cloned: TemplateConfig = config.clone(); + assert_eq!(cloned.component_name, "svc"); + assert_eq!(cloned.template_type, TemplateType::Service); + let debugged: String = format!("{config:?}"); + assert!(debugged.contains("svc")); + assert!(debugged.contains("./application")); +} + +#[test] +fn test_template_error_variants_display_messages() { + let invalid_type: TemplateError = TemplateError::InvalidTemplateType("bogus".to_string()); + assert_eq!(invalid_type.to_string(), "Invalid template type: bogus"); + let invalid_sub: TemplateError = TemplateError::InvalidModelSubType("bogus".to_string()); + assert_eq!(invalid_sub.to_string(), "Invalid model subtype: bogus"); + let exists: TemplateError = TemplateError::DirectoryExists("./a/b".to_string()); + assert_eq!(exists.to_string(), "Directory './a/b' already exists"); +} + +#[test] +fn test_template_error_from_io() { + let io_error: io::Error = io::Error::new(io::ErrorKind::PermissionDenied, "denied"); + let template_error: TemplateError = TemplateError::from(io_error); + assert_eq!(template_error.to_string(), "IO error: denied"); +} + +#[test] +fn test_template_error_debug_names_variant() { + let error: TemplateError = TemplateError::InvalidTemplateType("x".to_string()); + let debugged: String = format!("{error:?}"); + assert!(debugged.contains("InvalidTemplateType")); +} diff --git a/cli/tests/template/mod.rs b/cli/tests/template/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/cli/tests/template/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/compress/src/brotli/fn.rs b/compress/src/brotli/fn.rs index 39f19058..24381bd3 100644 --- a/compress/src/brotli/fn.rs +++ b/compress/src/brotli/fn.rs @@ -1,45 +1,45 @@ use super::*; -/// Compresses the given data using Gzip compression. +/// Compresses the given data using the Brotli compression algorithm. /// -/// This function takes a byte slice of data and compresses it using the Gzip compression algorithm. -/// If the compression succeeds, the resulting compressed data is returned as a `Cow>`. -/// If any error occurs during the compression process, an empty `Vec` is returned. +/// The bytes are written through a `CompressorWriter` from the `brotli` crate, so the +/// produced output is a real Brotli stream that any `Content-Encoding: br` consumer +/// can decode. An empty owned `Vec` is returned when compression fails. /// -/// # Parameters -/// - `data` - A reference to a byte slice (`&[u8]`) containing the data to be compressed. +/// # Arguments +/// +/// - `&'_ [u8]` - the bytes to compress. +/// - `usize` - the buffer size used by the compressor. /// /// # Returns -/// - `Cow<[u8]>` - The compressed data as a `Cow<[u8]>`. The compressed data is returned as an -/// owned `Vec`. If compression fails, an empty owned `Vec` is returned. -pub fn encode(data: &'_ [u8]) -> Cow<'_, [u8]> { - let mut encoder: GzEncoder> = GzEncoder::new(Vec::new(), Compression::default()); +/// +/// - `Cow<'_, [u8]>`: the compressed bytes, or an empty owned `Vec` on failure. +pub fn encode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { + let mut encoder: CompressorWriter> = CompressorWriter::new( + Vec::new(), + buffer_size, + BROTLI_DEFAULT_QUALITY, + BROTLI_DEFAULT_WINDOW_BITS, + ); if encoder.write_all(data).is_err() { return Cow::Owned(Vec::new()); } - Cow::Owned( - encoder - .finish() - .unwrap_or_else(|_: std::io::Error| Vec::new()), - ) + Cow::Owned(encoder.into_inner()) } -/// Decompresses the given data using the specified decompressor. +/// Decompresses Brotli-compressed data. /// -/// This function takes a byte slice of compressed data and decompresses it using -/// a decompressor, returning the result as a `Cow>`. If decompression is successful, -/// the decompressed data is returned as an owned `Vec`. In case of an error, an empty -/// `Vec` is returned. +/// Decoding uses the `Decompressor` from the `brotli` crate. An empty owned `Vec` +/// is returned when the input is not a valid Brotli stream. /// -/// # Parameters -/// - `data` - A reference to a byte slice (`&[u8]`) containing the compressed data to be decoded. -/// - `buffer_size` - The buffer size to use for the decompression process. A larger buffer size can -/// improve performance for larger datasets. +/// # Arguments +/// +/// - `&'_ [u8]` - the Brotli-compressed bytes to decode. +/// - `usize` - the buffer size used by the decompressor. /// /// # Returns -/// - `Cow<[u8]>` - The decompressed data as a `Cow<[u8]>`. If decompression is successful, the -/// decompressed data is returned as an owned `Vec`. In case of an error, an empty owned -/// `Vec` is returned. +/// +/// - `Cow<'_, [u8]>`: the decompressed bytes, or an empty owned `Vec` on failure. pub fn decode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { let mut decompressor: Decompressor<&[u8]> = Decompressor::new(data, buffer_size); let mut decompressed_data: Vec = Vec::new(); diff --git a/compress/src/compress/const.rs b/compress/src/compress/const.rs index 6a46d818..8ad8eb6d 100644 --- a/compress/src/compress/const.rs +++ b/compress/src/compress/const.rs @@ -12,3 +12,9 @@ pub const CONTENT_ENCODING_BROTLI: &str = "br"; /// An empty string. pub const EMPTY_STR: &str = ""; + +/// The default brotli compression quality. +pub const BROTLI_DEFAULT_QUALITY: u32 = 5; + +/// The default brotli sliding window size, in bits. +pub const BROTLI_DEFAULT_WINDOW_BITS: u32 = 22; diff --git a/compress/src/compress/impl.rs b/compress/src/compress/impl.rs index 1e43ada1..a59a0a12 100644 --- a/compress/src/compress/impl.rs +++ b/compress/src/compress/impl.rs @@ -17,7 +17,7 @@ impl FromStr for Compress { /// /// # Arguments /// - /// - `data` - The string to parse, which should be a compression algorithm name. + /// - `&str` - The string to parse, which should be a compression algorithm name. /// /// # Returns /// @@ -39,6 +39,18 @@ impl FromStr for Compress { /// This allows the `Compress` enum variants to be formatted as strings, /// typically used for outputting the `Content-Encoding` header value. impl fmt::Display for Compress { + /// Formats the `Compress` value as its `Content-Encoding` header token. + /// + /// `Compress::Unknown` formats as the empty string, since it has no + /// `Content-Encoding` representation. + /// + /// # Arguments + /// + /// - `&mut fmt::Formatter<'_>` - The formatter to write the value into. + /// + /// # Returns + /// + /// - `fmt::Result` - The result of the formatting operation. #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let display_str: &str = match *self { @@ -60,8 +72,7 @@ impl Compress { /// /// # Returns /// - /// - `true` if the instance is of type `Unknown`. - /// - `false` otherwise. + /// - `bool`: `true` if the instance is of type `Unknown`, `false` otherwise. #[inline(always)] pub fn is_unknown(&self) -> bool { *self == Self::Unknown @@ -74,12 +85,13 @@ impl Compress { /// /// # Arguments /// - /// - `header` - The HTTP header from which the compression type is to be extracted. + /// - `&HashMap>` - The HTTP header from which + /// the compression type is to be extracted. /// /// # Returns /// - /// - The `Compress` value corresponding to the `Content-Encoding` header, or `Compress::Unknown` - /// if the header does not match any known compression types. + /// - `Self` - The `Compress` value corresponding to the `Content-Encoding` header, or + /// `Compress::Unknown` if the header does not match any known compression types. #[inline(always)] pub fn from(header: &HashMap>) -> Self { header @@ -97,16 +109,16 @@ impl Compress { /// - `Br` - Decompresses using Brotli compression. /// - `Unknown` - Returns the input data as-is (no decompression performed). /// - /// # Parameters + /// # Arguments /// - /// - `data` - A reference to a byte slice (`&[u8]`) containing the compressed data to be decoded. - /// - `buffer_size` - The buffer size to use for the decompression process. A larger buffer size can + /// - `&'a [u8]` - A reference to a byte slice containing the compressed data to be decoded. + /// - `usize` - The buffer size to use for the decompression process. A larger buffer size can /// improve performance for larger datasets. /// /// # Returns /// - /// - `Cow<[u8]>` - The decompressed data as a `Cow<[u8]>`. If the compression algorithm - /// is `Unknown`, the original data is returned unchanged, as a borrowed reference. Otherwise, + /// - `Cow<'a, [u8]>` - The decompressed data. If the compression algorithm + /// is `Unknown`, the original data is returned unchanged, as an owned buffer. Otherwise, /// the decompressed data is returned as an owned `Vec`. pub fn decode<'a>(&self, data: &'a [u8], buffer_size: usize) -> Cow<'a, [u8]> { match self { @@ -126,22 +138,22 @@ impl Compress { /// - `Br` - Compresses using Brotli compression. /// - `Unknown` - Returns the input data as-is (no compression performed). /// - /// # Parameters + /// # Arguments /// - /// - `data` - A reference to a byte slice (`&[u8]`) containing the data to be compressed. - /// - `buffer_size` - The buffer size to use for the compression process. A larger buffer size can + /// - `&'a [u8]` - A reference to a byte slice containing the data to be compressed. + /// - `usize` - The buffer size to use for the compression process. A larger buffer size can /// improve performance for larger datasets. /// /// # Returns /// - /// - `Cow<[u8]>` - The compressed data as a `Cow<[u8]>`. If the compression algorithm - /// is `Unknown`, the original data is returned unchanged, as a borrowed reference. Otherwise, + /// - `Cow<'a, [u8]>` - The compressed data. If the compression algorithm + /// is `Unknown`, the original data is returned unchanged, as an owned buffer. Otherwise, /// the compressed data is returned as an owned `Vec`. pub fn encode<'a>(&self, data: &'a [u8], buffer_size: usize) -> Cow<'a, [u8]> { match self { Self::Gzip => gzip::encode(data, buffer_size), Self::Deflate => deflate::encode(data, buffer_size), - Self::Br => brotli::encode(data), + Self::Br => brotli::encode(data, buffer_size), Self::Unknown => Cow::Owned(data.to_vec()), } } diff --git a/compress/src/deflate/fn.rs b/compress/src/deflate/fn.rs index fd090e30..b5592739 100644 --- a/compress/src/deflate/fn.rs +++ b/compress/src/deflate/fn.rs @@ -9,13 +9,13 @@ use super::*; /// /// # Arguments /// -/// - `data` - A reference to a byte slice (`&[u8]`) containing the data to be compressed. -/// - `buffer_size` - The buffer size to use for the buffered writer. A larger buffer size can +/// - `&'_ [u8]` - A reference to a byte slice containing the data to be compressed. +/// - `usize` - The buffer size to use for the buffered writer. A larger buffer size can /// improve performance for larger datasets. /// /// # Returns /// -/// - `Cow<[u8]>` - The compressed data as a `Cow<[u8]>`. If compression is successful, the +/// - `Cow<'_, [u8]>` - The compressed data. If compression is successful, the /// compressed data is returned as an owned `Vec`. If an error occurs, an empty owned `Vec` /// is returned. pub fn encode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { @@ -26,11 +26,7 @@ pub fn encode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { return Cow::Owned(Vec::new()); } match buffered_writer.into_inner() { - Ok(encoder) => Cow::Owned( - encoder - .finish() - .unwrap_or_else(|_: std::io::Error| Vec::new()), - ), + Ok(encoder) => Cow::Owned(encoder.finish().unwrap_or_else(|_: io::Error| Vec::new())), Err(_) => Cow::Owned(Vec::new()), } } @@ -39,12 +35,12 @@ pub fn encode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { /// /// # Arguments /// -/// - `&[u8]` - The compressed data to decode. +/// - `&'_ [u8]` - A reference to a byte slice containing the compressed data to decode. /// - `usize` - The buffer size for decompression. /// /// # Returns /// -/// - `Cow<[u8]>` - The decompressed data. +/// - `Cow<'_, [u8]>` - The decompressed data. pub fn decode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { let decoder: DeflateDecoder<&[u8]> = DeflateDecoder::new(data); let mut buffered_reader: BufReader> = diff --git a/compress/src/gzip/fn.rs b/compress/src/gzip/fn.rs index d1060ab6..ea290aad 100644 --- a/compress/src/gzip/fn.rs +++ b/compress/src/gzip/fn.rs @@ -9,13 +9,13 @@ use super::*; /// /// # Arguments /// -/// - `data` - A reference to a byte slice (`&[u8]`) containing the data to be compressed. -/// - `buffer_size` - The buffer size to use for the buffered writer. A larger buffer size can improve +/// - `&'_ [u8]` - A reference to a byte slice containing the data to be compressed. +/// - `usize` - The buffer size to use for the buffered writer. A larger buffer size can improve /// performance for larger datasets. /// /// # Returns /// -/// - `Cow<[u8]>` - The compressed data as a `Cow<[u8]>`. If compression is successful, the +/// - `Cow<'_, [u8]>` - The compressed data. If compression is successful, the /// compressed data is returned as an owned `Vec`. If an error occurs, an empty owned `Vec` /// is returned. pub fn encode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { @@ -26,11 +26,7 @@ pub fn encode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { return Cow::Owned(Vec::new()); } match buffered_writer.into_inner() { - Ok(encoder) => Cow::Owned( - encoder - .finish() - .unwrap_or_else(|_: std::io::Error| Vec::new()), - ), + Ok(encoder) => Cow::Owned(encoder.finish().unwrap_or_else(|_: io::Error| Vec::new())), Err(_) => Cow::Owned(Vec::new()), } } @@ -44,13 +40,13 @@ pub fn encode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { /// /// # Arguments /// -/// - `data` - A reference to a byte slice (`&[u8]`) containing the Gzip-compressed data. -/// - `buffer_size` - The buffer size to use for the buffered reader. A larger buffer size can improve +/// - `&'_ [u8]` - A reference to a byte slice containing the Gzip-compressed data. +/// - `usize` - The buffer size to use for the buffered reader. A larger buffer size can improve /// performance for larger datasets. /// /// # Returns /// -/// - `Cow<[u8]>` - The decompressed data as a `Cow<[u8]>`. If decompression is successful, the +/// - `Cow<'_, [u8]>` - The decompressed data. If decompression is successful, the /// decompressed data is returned as an owned `Vec`. If an error occurs, an empty owned `Vec` /// is returned. pub fn decode(data: &'_ [u8], buffer_size: usize) -> Cow<'_, [u8]> { diff --git a/compress/src/lib.rs b/compress/src/lib.rs index dc57e19e..55b04f1b 100644 --- a/compress/src/lib.rs +++ b/compress/src/lib.rs @@ -18,12 +18,12 @@ use std::{ borrow::Cow, collections::HashMap, fmt, - io::{BufReader, BufWriter, Read, prelude::*}, + io::{self, BufReader, BufWriter, Read, prelude::*}, str::FromStr, }; use { - ::brotli::Decompressor, + ::brotli::{CompressorWriter, Decompressor}, core::hash::BuildHasherDefault, flate2::{ Compression, diff --git a/compress/tests/compress/fn.rs b/compress/tests/compress/fn.rs index 352af5e8..11f96905 100644 --- a/compress/tests/compress/fn.rs +++ b/compress/tests/compress/fn.rs @@ -1,16 +1,187 @@ use super::*; #[test] -fn test() { - let headers: HashMap<_, _, BuildHasherDefault> = +fn from_str_maps_known_encoding_names() { + let gzip: Compress = "gzip".parse::().unwrap_or_default(); + let deflate: Compress = "deflate".parse::().unwrap_or_default(); + let brotli: Compress = "br".parse::().unwrap_or_default(); + let unknown: Compress = "lzma".parse::().unwrap_or_default(); + let empty: Compress = "".parse::().unwrap_or_default(); + assert_eq!(gzip, Compress::Gzip); + assert_eq!(deflate, Compress::Deflate); + assert_eq!(brotli, Compress::Br); + assert_eq!(unknown, Compress::Unknown); + assert_eq!(empty, Compress::Unknown); +} + +#[test] +fn from_str_is_case_insensitive() { + let upper: Compress = "GZIP".parse::().unwrap_or_default(); + let mixed: Compress = "DeFlAtE".parse::().unwrap_or_default(); + let brotli: Compress = "BR".parse::().unwrap_or_default(); + assert_eq!(upper, Compress::Gzip); + assert_eq!(mixed, Compress::Deflate); + assert_eq!(brotli, Compress::Br); +} + +#[test] +fn display_renders_content_encoding_token() { + assert_eq!(Compress::Gzip.to_string(), "gzip"); + assert_eq!(Compress::Deflate.to_string(), "deflate"); + assert_eq!(Compress::Br.to_string(), "br"); + assert_eq!(Compress::Unknown.to_string(), ""); +} + +#[test] +fn display_round_trips_through_from_str() { + let variants: Vec = vec![ + Compress::Gzip, + Compress::Deflate, + Compress::Br, + Compress::Unknown, + ]; + for variant in variants { + let text: String = variant.to_string(); + let parsed: Compress = text.parse::().unwrap_or_default(); + if variant == Compress::Unknown { + assert_eq!(parsed, Compress::Unknown); + } else { + assert_eq!(parsed, variant); + } + } +} + +#[test] +fn is_unknown_is_true_only_for_unknown() { + assert!(Compress::Unknown.is_unknown()); + assert!(!Compress::Gzip.is_unknown()); + assert!(!Compress::Deflate.is_unknown()); + assert!(!Compress::Br.is_unknown()); +} + +#[test] +fn default_is_unknown() { + assert_eq!(Compress::default(), Compress::Unknown); + assert!(Compress::default().is_unknown()); +} + +#[test] +fn from_reads_content_encoding_header() { + let mut headers: HashMap> = + HashMap::with_hasher(BuildHasherDefault::default()); + assert_eq!(Compress::from(&headers), Compress::Unknown); + headers.insert(String::from("content-encoding"), String::from("gzip")); + assert_eq!(Compress::from(&headers), Compress::Gzip); + headers.insert(String::from("content-encoding"), String::from("deflate")); + assert_eq!(Compress::from(&headers), Compress::Deflate); + headers.insert(String::from("content-encoding"), String::from("br")); + assert_eq!(Compress::from(&headers), Compress::Br); + headers.insert(String::from("content-encoding"), String::from("lzma")); + assert_eq!(Compress::from(&headers), Compress::Unknown); +} + +#[test] +fn from_ignores_unrelated_headers() { + let mut headers: HashMap> = HashMap::with_hasher(BuildHasherDefault::default()); - let data: Vec = vec![]; - let body: Cow<'_, [u8]> = Compress::from(&headers).decode(&data, 1_024_000); - assert_eq!(*body, data); - let _: Cow<'_, [u8]> = Compress::Gzip.encode(&[], 1_024_000); - let _: Cow<'_, [u8]> = Compress::Deflate.encode(&[], 1_024_000); - let _: Cow<'_, [u8]> = Compress::Br.encode(&[], 1_024_000); - let _: Cow<'_, [u8]> = Compress::Gzip.decode(&[], 1_024_000); - let _: Cow<'_, [u8]> = Compress::Deflate.decode(&[], 1_024_000); - let _: Cow<'_, [u8]> = Compress::Br.decode(&[], 1_024_000); + headers.insert(String::from("accept"), String::from("gzip")); + assert_eq!(Compress::from(&headers), Compress::Unknown); +} + +#[test] +fn gzip_round_trip_restores_payload() { + let payload: Vec = b"hyperlane gzip round trip payload".to_vec(); + let encoded: Cow<'_, [u8]> = Compress::Gzip.encode(&payload, 1_024_000); + let decoded: Cow<'_, [u8]> = Compress::Gzip.decode(&encoded, 1_024_000); + assert_eq!(*decoded, payload); +} + +#[test] +fn deflate_round_trip_restores_payload() { + let payload: Vec = b"hyperlane deflate round trip payload".to_vec(); + let encoded: Cow<'_, [u8]> = Compress::Deflate.encode(&payload, 1_024_000); + let decoded: Cow<'_, [u8]> = Compress::Deflate.decode(&encoded, 1_024_000); + assert_eq!(*decoded, payload); +} + +#[test] +fn brotli_round_trip_restores_payload() { + let payload: Vec = b"hyperlane brotli round trip payload".to_vec(); + let encoded: Cow<'_, [u8]> = Compress::Br.encode(&payload, 1_024_000); + let decoded: Cow<'_, [u8]> = Compress::Br.decode(&encoded, 1_024_000); + assert_eq!(*decoded, payload); +} + +#[test] +fn every_known_variant_round_trips() { + let payload: Vec = b"hyperlane all variants round trip".to_vec(); + let variants: Vec = vec![Compress::Gzip, Compress::Deflate, Compress::Br]; + for variant in variants { + let encoded: Cow<'_, [u8]> = variant.encode(&payload, 1_024_000); + let decoded: Cow<'_, [u8]> = variant.decode(&encoded, 1_024_000); + assert_eq!(*decoded, payload); + } +} + +#[test] +fn unknown_passes_data_through_unchanged() { + let payload: Vec = b"hyperlane passthrough payload".to_vec(); + let encoded: Cow<'_, [u8]> = Compress::Unknown.encode(&payload, 1_024_000); + let decoded: Cow<'_, [u8]> = Compress::Unknown.decode(&payload, 1_024_000); + assert_eq!(*encoded, payload); + assert_eq!(*decoded, payload); +} + +#[test] +fn empty_input_round_trips_for_every_variant() { + let payload: Vec = Vec::new(); + let variants: Vec = vec![ + Compress::Gzip, + Compress::Deflate, + Compress::Br, + Compress::Unknown, + ]; + for variant in variants { + let encoded: Cow<'_, [u8]> = variant.encode(&payload, 1_024_000); + let decoded: Cow<'_, [u8]> = variant.decode(&encoded, 1_024_000); + assert_eq!(*decoded, payload); + } +} + +#[test] +fn corrupt_input_decodes_to_empty_for_known_variants() { + let garbage: Vec = vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]; + let gzip: Cow<'_, [u8]> = Compress::Gzip.decode(&garbage, 1_024_000); + let deflate: Cow<'_, [u8]> = Compress::Deflate.decode(&garbage, 1_024_000); + let brotli: Cow<'_, [u8]> = Compress::Br.decode(&garbage, 1_024_000); + assert!(gzip.is_empty()); + assert!(deflate.is_empty()); + assert!(brotli.is_empty()); +} + +#[test] +fn large_payload_round_trips_across_all_variants() { + let mut payload: Vec = Vec::with_capacity(65_536); + for index in 0..65_536u32 { + payload.push((index % 251) as u8); + } + let variants: Vec = vec![Compress::Gzip, Compress::Deflate, Compress::Br]; + for variant in variants { + let encoded: Cow<'_, [u8]> = variant.encode(&payload, 1_024_000); + let decoded: Cow<'_, [u8]> = variant.decode(&encoded, 1_024_000); + assert_eq!(decoded.len(), payload.len()); + assert_eq!(*decoded, payload); + } +} + +#[test] +fn variants_are_ordered_and_hashable() { + assert!(Compress::Gzip < Compress::Deflate); + assert!(Compress::Deflate < Compress::Br); + assert!(Compress::Br < Compress::Unknown); + let mut set: HashSet = HashSet::new(); + set.insert(Compress::Gzip); + set.insert(Compress::Gzip); + set.insert(Compress::Br); + assert_eq!(set.len(), 2); } diff --git a/compress/tests/mod.rs b/compress/tests/mod.rs index b28bf5d5..2b55c5ba 100644 --- a/compress/tests/mod.rs +++ b/compress/tests/mod.rs @@ -2,6 +2,9 @@ mod compress; use http_compress::*; -use std::{borrow::Cow, collections::HashMap}; +use std::{ + borrow::Cow, + collections::{HashMap, HashSet}, +}; use {core::hash::BuildHasherDefault, twox_hash::XxHash3_64}; diff --git a/constant/tests/constant/fn.rs b/constant/tests/constant/fn.rs new file mode 100644 index 00000000..ccdda459 --- /dev/null +++ b/constant/tests/constant/fn.rs @@ -0,0 +1,255 @@ +use super::*; + +#[test] +fn http_methods_are_uppercase_tokens() { + assert_eq!(GET, "GET"); + assert_eq!(POST, "POST"); + assert_eq!(PUT, "PUT"); + assert_eq!(DELETE, "DELETE"); + assert_eq!(HEAD, "HEAD"); + assert_eq!(OPTIONS, "OPTIONS"); + assert_eq!(PATCH, "PATCH"); +} + +#[test] +fn header_keys_are_lowercase_tokens() { + assert_eq!(CONTENT_TYPE, "content-type"); + assert_eq!(CONTENT_LENGTH, "content-length"); + assert_eq!(CONTENT_ENCODING, "content-encoding"); + assert_eq!(TRANSFER_ENCODING, "transfer-encoding"); + assert_eq!(ACCEPT_ENCODING, "accept-encoding"); +} + +#[test] +fn header_keys_contain_no_whitespace() { + let keys: Vec<&str> = vec![ + CONTENT_TYPE, + CONTENT_LENGTH, + CONTENT_ENCODING, + TRANSFER_ENCODING, + ACCEPT_ENCODING, + SERVER, + HOST, + ]; + for key in keys { + assert!(!key.contains(' '), "header key has a space: {key}"); + assert!(!key.contains('\t'), "header key has a tab: {key}"); + assert_eq!(key, key.to_lowercase()); + } +} + +#[test] +fn content_type_values_are_lowercase_media_types() { + assert_eq!(APPLICATION_JSON, "application/json"); + assert_eq!(TEXT_HTML, "text/html"); + assert_eq!(TEXT_PLAIN, "text/plain"); +} + +#[test] +fn media_types_are_lowercase_and_slash_separated() { + let types: Vec<&str> = vec![APPLICATION_JSON, TEXT_HTML, TEXT_PLAIN]; + for media in types { + assert_eq!(media, media.to_lowercase()); + assert!(media.contains('/'), "media type lacks a slash: {media}"); + let parts: Vec<&str> = media.split('/').collect(); + assert_eq!(parts.len(), 2, "media type malformed: {media}"); + } +} + +#[test] +fn byte_views_match_their_string_constants() { + assert_eq!(SPACE_BYTES, SPACE.as_bytes()); + assert_eq!(TAB_BYTES, TAB.as_bytes()); + assert_eq!(BR_BYTES, BR.as_bytes()); + assert_eq!(DOUBLE_BR_BYTES, DOUBLE_BR.as_bytes()); + assert_eq!(COLON_BYTES, COLON.as_bytes()); + assert_eq!(COLON_SPACE_BYTES, COLON_SPACE.as_bytes()); + assert_eq!(EQUAL_BYTES, EQUAL.as_bytes()); + assert_eq!(AND_BYTES, AND.as_bytes()); + assert_eq!(COMMA_BYTES, COMMA.as_bytes()); + assert_eq!(HASH_BYTES, HASH.as_bytes()); + assert_eq!(SEMICOLON_BYTES, SEMICOLON.as_bytes()); + assert_eq!(QUERY_BYTES, QUERY.as_bytes()); + assert_eq!(POINT_BYTES, POINT.as_bytes()); + assert_eq!(ROOT_PATH_BYTES, ROOT_PATH.as_bytes()); + assert_eq!(HTTP_BR_BYTES, HTTP_BR.as_bytes()); + assert_eq!(HTTP_DOUBLE_BR_BYTES, HTTP_DOUBLE_BR.as_bytes()); + assert_eq!(HYPERLANE_BYTES, HYPERLANE.as_bytes()); + assert_eq!(LOCALHOST_BYTES, LOCALHOST.as_bytes()); + assert_eq!(DEFAULT_HTTP_PATH_BYTES, DEFAULT_HTTP_PATH.as_bytes()); +} + +#[test] +fn single_byte_views_take_the_first_byte() { + assert_eq!(SPACE_U8, SPACE_BYTES[0]); + assert_eq!(TAB_U8, TAB_BYTES[0]); + assert_eq!(COLON_U8, COLON_BYTES[0]); + assert_eq!(EQUAL_U8, EQUAL_BYTES[0]); + assert_eq!(AND_U8, AND_BYTES[0]); + assert_eq!(COMMA_U8, COMMA_BYTES[0]); + assert_eq!(HASH_U8, HASH_BYTES[0]); + assert_eq!(SEMICOLON_U8, SEMICOLON_BYTES[0]); + assert_eq!(QUERY_U8, QUERY_BYTES[0]); + assert_eq!(POINT_U8, POINT_BYTES[0]); + assert_eq!(ZERO_STR_U8, ZERO_STR_BYTES[0]); +} + +#[test] +fn delimiter_constants_are_single_ascii_characters() { + let pairs: Vec<(&str, &str)> = vec![ + (SPACE, " "), + (TAB, "\t"), + (COLON, ":"), + (EQUAL, "="), + (AND, "&"), + (COMMA, ","), + (HASH, "#"), + (SEMICOLON, ";"), + (QUERY, "?"), + (POINT, "."), + ]; + for (actual, expected) in pairs { + assert_eq!(actual, expected); + assert_eq!(actual.len(), 1); + } +} + +#[test] +fn multi_char_delimiters_have_expected_content() { + assert_eq!(BR, "\n"); + assert_eq!(DOUBLE_BR, "\n\n"); + assert_eq!(HTTP_BR, "\r\n"); + assert_eq!(HTTP_DOUBLE_BR, "\r\n\r\n"); + assert_eq!(COLON_SPACE, ": "); + assert_eq!(SEMICOLON_SPACE, "; "); +} + +#[test] +fn empty_and_zero_constants_agree() { + assert_eq!(EMPTY_STR, ""); + assert!(EMPTY_STR_BYTES.is_empty()); + assert_eq!(ZERO_STR, "0"); + assert_eq!(ZERO_STR_BYTES, ZERO_STR.as_bytes()); +} + +#[test] +fn http_version_tokens_are_prefixed() { + assert_eq!(HTTP_VERSION_1_0, "HTTP/1.0"); + assert_eq!(HTTP_VERSION_1_1, "HTTP/1.1"); + assert_eq!(HTTP_VERSION_2, "HTTP/2"); + for version in [HTTP_VERSION_1_0, HTTP_VERSION_1_1, HTTP_VERSION_2] { + assert!(version.starts_with("HTTP/")); + } +} + +#[test] +fn default_path_and_root_path_agree() { + assert_eq!(DEFAULT_HTTP_PATH, "/"); + assert_eq!(ROOT_PATH, "/"); + assert_eq!(DEFAULT_HTTP_PATH_BYTES, ROOT_PATH_BYTES); +} + +#[test] +fn security_level_limits_are_ordered() { + let low_body: usize = DEFAULT_LOW_SECURITY_MAX_BODY_SIZE; + let high_body: usize = DEFAULT_HIGH_SECURITY_MAX_BODY_SIZE; + let low_path: usize = DEFAULT_LOW_SECURITY_MAX_PATH_SIZE; + let high_path: usize = DEFAULT_HIGH_SECURITY_MAX_PATH_SIZE; + let low_headers: usize = DEFAULT_LOW_SECURITY_MAX_HEADER_COUNT; + let high_headers: usize = DEFAULT_HIGH_SECURITY_MAX_HEADER_COUNT; + assert!(low_body >= high_body); + assert!(low_path >= high_path); + assert!(low_headers >= high_headers); + assert!(high_body > 0); + assert!(high_path > 0); + assert!(high_headers > 0); +} + +#[test] +fn buffer_defaults_are_non_zero() { + let buffer: usize = DEFAULT_BUFFER_SIZE; + let request_line: usize = REQUEST_LINE_BUFFER_CAPACITY; + let header_line: usize = HEADER_LINE_BUFFER_CAPACITY; + let pooled_count: usize = MAX_POOLED_READ_BUFFERS; + let pooled_size: usize = MAX_POOLED_READ_BUFFER_SIZE; + assert!(buffer > 0); + assert!(request_line > 0); + assert!(header_line > 0); + assert!(pooled_count > 0); + assert!(pooled_size > 0); +} + +#[test] +fn redirect_limit_is_bounded() { + let limit: usize = DEFAULT_MAX_REDIRECT_TIMES; + assert!(limit > 0); + assert!(limit <= 10); +} + +#[test] +fn hyperlane_name_variants_are_consistent() { + assert_eq!(HYPERLANE, "hyperlane"); + assert_eq!(HYPERLANE_PASCAL_CASE, "Hyperlane"); + assert_eq!(HYPERLANE_UPPERCASE, "HYPERLANE"); + assert_eq!( + HYPERLANE_PASCAL_CASE_BYTES, + HYPERLANE_PASCAL_CASE.as_bytes() + ); + assert_eq!(HYPERLANE_UPPERCASE_BYTES, HYPERLANE_UPPERCASE.as_bytes()); + assert_eq!(HYPERLANE_BYTES, HYPERLANE.as_bytes()); +} + +#[test] +fn log_level_names_have_expected_values() { + assert_eq!(ERROR, "error"); + assert_eq!(WARNING, "warning"); + assert_eq!(INFO, "info"); + assert_eq!(DEBUG, "debug"); + assert_eq!(TRACE, "TRACE"); + assert_eq!(SUCCESS, "success"); + assert_eq!(FAIL, "fail"); +} + +#[test] +fn log_level_byte_views_match() { + assert_eq!(WARNING_BYTES, WARNING.as_bytes()); + assert_eq!(SUCCESS_BYTES, SUCCESS.as_bytes()); + assert_eq!(FAIL_BYTES, FAIL.as_bytes()); + assert_eq!(ERROR_BYTES, ERROR.as_bytes()); + assert_eq!(INFO_BYTES, INFO.as_bytes()); + assert_eq!(DEBUG_BYTES, DEBUG.as_bytes()); + assert_eq!(PLAIN_BYTES, PLAIN.as_bytes()); + assert_eq!(BINARY_BYTES, BINARY.as_bytes()); +} + +#[test] +fn bracket_pairs_are_balanced() { + assert_eq!(LEFT_BRACKET, "{"); + assert_eq!(RIGHT_BRACKET, "}"); + assert_eq!(LEFT_SQUARE_BRACKET, "["); + assert_eq!(RIGHT_SQUARE_BRACKET, "]"); + assert_eq!(LEFT_PAREN, "("); + assert_eq!(RIGHT_PAREN, ")"); + assert_eq!(LEFT_BRACKET_BYTES, LEFT_BRACKET.as_bytes()); + assert_eq!(RIGHT_BRACKET_BYTES, RIGHT_BRACKET.as_bytes()); + assert_eq!(LEFT_PAREN_BYTES, LEFT_PAREN.as_bytes()); + assert_eq!(RIGHT_PAREN_BYTES, RIGHT_PAREN.as_bytes()); + assert_eq!(LEFT_SQUARE_BRACKET_BYTES, LEFT_SQUARE_BRACKET.as_bytes()); + assert_eq!(RIGHT_SQUARE_BRACKET_BYTES, RIGHT_SQUARE_BRACKET.as_bytes()); +} + +#[test] +fn default_ports_are_standard() { + assert_eq!(DEFAULT_HTTP_PORT, 80); + assert_eq!(DEFAULT_HTTPS_PORT, 443); + assert_eq!(DEFAULT_HTTP_PORT_STR, "80"); + assert_eq!(DEFAULT_HTTPS_PORT_STR, "443"); +} + +#[test] +fn loopback_addresses_are_local() { + assert_eq!(LOCALHOST, "localhost"); + assert_eq!(LOOPBACK, "127.0.0.1"); + assert_eq!(DEFAULT_HOST_IPV4_ADDR.to_string(), "0.0.0.0"); + assert_eq!(DEFAULT_IPV4_ADDR.to_string(), "0.0.0.0"); +} diff --git a/constant/tests/constant/mod.rs b/constant/tests/constant/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/constant/tests/constant/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/constant/tests/mod.rs b/constant/tests/mod.rs new file mode 100644 index 00000000..668730d7 --- /dev/null +++ b/constant/tests/mod.rs @@ -0,0 +1,3 @@ +mod r#constant; + +use http_constant::*; diff --git a/core/src/config/impl.rs b/core/src/config/impl.rs index b245b146..af89ca57 100644 --- a/core/src/config/impl.rs +++ b/core/src/config/impl.rs @@ -23,7 +23,7 @@ impl ServerConfig { /// /// # Arguments /// - /// - `AsRef` - The configuration. + /// - `C` - The configuration. /// /// # Returns /// diff --git a/core/src/config/struct.rs b/core/src/config/struct.rs index d2c10402..8862fdcc 100644 --- a/core/src/config/struct.rs +++ b/core/src/config/struct.rs @@ -10,7 +10,9 @@ pub struct ServerConfig { #[set(type(AsRef))] pub(super) address: String, /// The `TCP_NODELAY` option for sockets. + #[get(type(copy))] pub(super) nodelay: Option, /// The `IP_TTL` option for sockets. + #[get(type(copy))] pub(super) ttl: Option, } diff --git a/core/src/context/impl.rs b/core/src/context/impl.rs index 06498a15..4b4eb78f 100644 --- a/core/src/context/impl.rs +++ b/core/src/context/impl.rs @@ -74,7 +74,7 @@ impl<'a> From for &'a mut Context { /// /// # Returns /// - /// - `&mut Context` - A mutable reference to the `Context` at the given address. + /// - `&'a mut Context` - A mutable reference to the `Context` at the given address. /// /// # Safety /// @@ -126,7 +126,7 @@ impl AsRef for Context { /// /// # Returns /// - /// - `&Context` - A reference to the `Context` instance. + /// - `&Self` - A reference to the `Context` instance. #[inline(always)] fn as_ref(&self) -> &Self { let address: usize = self.into(); @@ -140,7 +140,7 @@ impl AsMut for Context { /// /// # Returns /// - /// - `&mut Context` - A mutable reference to the `Context` instance. + /// - `&mut Self` - A mutable reference to the `Context` instance. #[inline(always)] fn as_mut(&mut self) -> &mut Self { let address: usize = self.into(); @@ -203,7 +203,7 @@ impl Context { /// /// # Arguments /// - /// - `AsRef` - The name of the route parameter to retrieve. + /// - `T` - The name of the route parameter to retrieve. /// /// # Returns /// @@ -220,7 +220,7 @@ impl Context { /// /// # Arguments /// - /// - `AsRef` - The name of the route parameter to retrieve. + /// - `T` - The name of the route parameter to retrieve. /// /// # Returns /// @@ -241,15 +241,16 @@ impl Context { /// /// # Arguments /// - /// - `AsRef` - The key of the attribute to retrieve. + /// - `K` - The key of the attribute to retrieve. /// /// # Returns /// /// - `Option` - The attribute value if it exists and can be cast to the specified type. #[inline(always)] - pub fn try_get_attribute(&self, key: impl AsRef) -> Option + pub fn try_get_attribute(&self, key: K) -> Option where V: AnySendSyncClone, + K: AsRef, { self.get_attributes() .get(&Attribute::External(key.as_ref().to_owned()).to_string()) @@ -261,19 +262,20 @@ impl Context { /// /// # Arguments /// - /// - `AsRef` - The key of the attribute to retrieve. + /// - `K` - The key of the attribute to retrieve. /// /// # Returns /// - /// - `AnySendSyncClone` - The attribute value if it exists and can be cast to the specified type. + /// - `V` - The attribute value if it exists and can be cast to the specified type. /// /// # Panics /// /// - If the attribute is not found. #[inline(always)] - pub fn get_attribute(&self, key: impl AsRef) -> V + pub fn get_attribute(&self, key: K) -> V where V: AnySendSyncClone, + K: AsRef, { self.try_get_attribute(key).unwrap() } @@ -282,8 +284,8 @@ impl Context { /// /// # Arguments /// - /// - `AsRef` - The key of the attribute to set. - /// - `AnySendSyncClone` - The value of the attribute. + /// - `K` - The key of the attribute to set. + /// - `V` - The value of the attribute. /// /// # Returns /// @@ -305,7 +307,7 @@ impl Context { /// /// # Arguments /// - /// - `AsRef` - The key of the attribute to remove. + /// - `K` - The key of the attribute to remove. /// /// # Returns /// @@ -359,7 +361,7 @@ impl Context { /// /// # Returns /// - /// - `AnySendSyncClone` - The attribute value if it exists and can be cast to the specified type. + /// - `V` - The attribute value if it exists and can be cast to the specified type. /// /// # Panics /// @@ -377,7 +379,7 @@ impl Context { /// # Arguments /// /// - `InternalAttribute` - The internal attribute key to set. - /// - `AnySendSyncClone` - The value of the attribute. + /// - `V` - The value of the attribute. /// /// # Returns /// diff --git a/core/src/error/impl.rs b/core/src/error/impl.rs index 89828583..b5fc3bc9 100644 --- a/core/src/error/impl.rs +++ b/core/src/error/impl.rs @@ -4,18 +4,18 @@ use super::*; /// /// This allows using the `?` operator to automatically convert `IoError` /// into `ServerError::TcpBind` when binding to a TCP socket. -impl From for ServerError { +impl From for ServerError { /// Creates a new `ServerError::TcpBind` instance from a `IoError`. /// /// # Arguments /// - /// - `IoError` - The `IoError` to convert. + /// - `io::Error` - The `IoError` to convert. /// /// # Returns /// /// - `Self` - A new `ServerError::TcpBind` instance. #[inline(always)] - fn from(error: std::io::Error) -> Self { + fn from(error: io::Error) -> Self { ServerError::TcpBind(error.to_string()) } } diff --git a/core/src/hook/impl.rs b/core/src/hook/impl.rs index 24e5c1fb..c0678460 100644 --- a/core/src/hook/impl.rs +++ b/core/src/hook/impl.rs @@ -110,9 +110,8 @@ impl Hook { /// Creates a new `ServerHookHandler` from a trait object. /// - /// # Arguments - /// - /// - `ServerHook` - The trait object implementing `ServerHook`. + /// The hook implementation is chosen by the caller through the type + /// parameter `R`, so this function takes no runtime argument. /// /// # Returns /// @@ -154,23 +153,23 @@ impl PartialEq for HookType { fn eq(&self, other: &Self) -> bool { match (self, other) { (HookType::TaskPanic(order1, factory1), HookType::TaskPanic(order2, factory2)) => { - order1 == order2 && std::ptr::fn_addr_eq(*factory1, *factory2) + order1 == order2 && ptr::fn_addr_eq(*factory1, *factory2) } ( HookType::RequestError(order1, factory1), HookType::RequestError(order2, factory2), - ) => order1 == order2 && std::ptr::fn_addr_eq(*factory1, *factory2), + ) => order1 == order2 && ptr::fn_addr_eq(*factory1, *factory2), ( HookType::RequestMiddleware(order1, factory1), HookType::RequestMiddleware(order2, factory2), - ) => order1 == order2 && std::ptr::fn_addr_eq(*factory1, *factory2), + ) => order1 == order2 && ptr::fn_addr_eq(*factory1, *factory2), (HookType::Route(path1, factory1), HookType::Route(path2, factory2)) => { - path1 == path2 && std::ptr::fn_addr_eq(*factory1, *factory2) + path1 == path2 && ptr::fn_addr_eq(*factory1, *factory2) } ( HookType::ResponseMiddleware(order1, factory1), HookType::ResponseMiddleware(order2, factory2), - ) => order1 == order2 && std::ptr::fn_addr_eq(*factory1, *factory2), + ) => order1 == order2 && ptr::fn_addr_eq(*factory1, *factory2), _ => false, } } @@ -190,7 +189,7 @@ impl Hash for HookType { /// /// # Arguments /// - /// - `&mut Hasher` - The hasher to use. + /// - `&mut H` - The hasher to use. #[inline] fn hash(&self, state: &mut H) { match self { @@ -248,6 +247,15 @@ impl HookType { } } + /// Returns the hook factory carried by this `HookType`, when it has one. + /// + /// Route hooks store a pattern instead of a factory, so `None` is + /// returned for them. + /// + /// # Returns + /// + /// - `Option` - `Some(factory)` for the hook + /// variants that carry one, otherwise `None`. #[inline(always)] pub fn try_get_hook(&self) -> Option { match *self { diff --git a/core/src/hook/trait.rs b/core/src/hook/trait.rs index 75927859..49b9fab6 100644 --- a/core/src/hook/trait.rs +++ b/core/src/hook/trait.rs @@ -79,6 +79,6 @@ pub trait ServerHook: Send + Sync + 'static { /// /// # Returns /// - /// - `Status` - `Status::Continue` if the pipeline should proceed, `Status::Reject` if the pipeline should be aborted. + /// - `impl Future` - `Status::Continue` if the pipeline should proceed, `Status::Reject` if the pipeline should be aborted. fn handle(self, stream: &mut Stream, ctx: &mut Context) -> impl Future + Send; } diff --git a/core/src/lib.rs b/core/src/lib.rs index 7ce8a512..dd455fae 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -28,6 +28,7 @@ use std::{ io::{self, Write, stderr, stdout}, mem, pin::Pin, + ptr, sync::Arc, }; diff --git a/core/src/route/impl.rs b/core/src/route/impl.rs index 4e619247..4484e0e9 100644 --- a/core/src/route/impl.rs +++ b/core/src/route/impl.rs @@ -52,7 +52,7 @@ impl Hash for RoutePattern { /// /// # Arguments /// - /// - `&mut Hasher` - The hasher to use. + /// - `&mut H` - The hasher to use. #[inline(always)] fn hash(&self, state: &mut H) { self.get_0().hash(state); @@ -255,7 +255,7 @@ impl Hash for RouteSegment { /// /// # Arguments /// - /// - `&mut Hasher` - The hasher to use. + /// - `&mut H` - The hasher to use. #[inline(always)] fn hash(&self, state: &mut H) { match self { @@ -460,12 +460,12 @@ impl RoutePattern { path_segments[idx..].join(DEFAULT_HTTP_PATH) } else { { - let val = path_segments.get(idx)?; + let val: &&str = path_segments.get(idx)?; val.to_string() } }; { - let mat = regex.find(&segment_value)?; + let mat: regex::Match<'_> = regex.find(&segment_value)?; if mat.start() != 0 || mat.end() != segment_value.len() { return None; } @@ -612,8 +612,14 @@ impl RouteMatcher { /// /// # Arguments /// + /// - `&'a self` - The route table to search. /// - `&mut Context` - The request context (for storing route params). /// - `&str` - The request path to resolve. + /// + /// # Returns + /// + /// - `Option<&'a ServerHookHandler>`: The matched handler, or `None` when no + /// route matched. pub fn try_resolve_route<'a>( &'a self, ctx: &mut Context, diff --git a/core/src/server/impl.rs b/core/src/server/impl.rs index 1cf61a6e..50b0c874 100644 --- a/core/src/server/impl.rs +++ b/core/src/server/impl.rs @@ -173,7 +173,7 @@ impl AsRef for Server { /// /// # Returns /// - /// - `&Server` - A reference to the `Server` instance. + /// - `&Self` - A reference to the `Server` instance. #[inline(always)] fn as_ref(&self) -> &Self { let address: usize = self.into(); @@ -187,7 +187,7 @@ impl AsMut for Server { /// /// # Returns /// - /// - `&mut Server` - A mutable reference to the `Server` instance. + /// - `&mut Self` - A mutable reference to the `Server` instance. #[inline(always)] fn as_mut(&mut self) -> &mut Self { let address: usize = self.into(); @@ -320,7 +320,7 @@ impl Server { /// /// # Arguments /// - /// - `AsRef` - The configuration. + /// - `C` - The configuration. /// /// # Returns /// @@ -406,15 +406,16 @@ impl Server { /// /// # Arguments /// - /// - `AsRef` - The route path pattern. + /// - `P` - The route path pattern. /// /// # Returns /// /// - `&mut Self` - Reference to self for method chaining. #[inline(always)] - pub fn route(&mut self, path: impl AsRef) -> &mut Self + pub fn route(&mut self, path: P) -> &mut Self where S: ServerHook, + P: AsRef, { self.get_mut_route_matcher() .add(path.as_ref(), Hook::factory::()) @@ -461,7 +462,7 @@ impl Server { /// /// # Arguments /// - /// - `AsRef` - The host address. + /// - `H` - The host address. /// - `u16` - The port number. /// /// # Returns @@ -541,9 +542,9 @@ impl Server { /// /// # Arguments /// - /// - `usize` - The address of the stream. - /// - `usize` - The address of the context. - /// - `Future + Send + 'static` - The hook to execute. + /// - `&'static self` - The server instance whose task-panic hooks are used. + /// - `usize` - The addresses of the stream and of the context. + /// - `F` - The hook to execute. /// /// # Safety /// @@ -585,10 +586,10 @@ impl Server { fn configure_stream(&self, stream: &TcpStream) { let config: &ServerConfig = self.get_server_config(); if let Some(nodelay) = config.try_get_nodelay() { - let _: Result<(), std::io::Error> = stream.set_nodelay(*nodelay); + let _: Result<(), io::Error> = stream.set_nodelay(nodelay); } if let Some(ttl) = config.try_get_ttl() { - let _: Result<(), std::io::Error> = stream.set_ttl(*ttl); + let _: Result<(), io::Error> = stream.set_ttl(ttl); } } @@ -780,6 +781,7 @@ impl Server { /// /// # Arguments /// + /// - `&'static self` - The server instance that owns the accept loop. /// - `&TcpListener` - A reference to the `TcpListener` to accept connections from. async fn tcp_accept(&'static self, tcp_listener: &TcpListener) { loop { @@ -812,7 +814,7 @@ impl Server { let bind_address: &String = self.get_server_config().get_address(); let tcp_listener: TcpListener = TcpListener::bind(&bind_address) .await - .map_err(|error: std::io::Error| Box::new(ServerError::from(error)))?; + .map_err(|error: io::Error| Box::new(ServerError::from(error)))?; let server: &'static Self = unsafe { self.leak() }; let (wait_sender, wait_receiver) = channel(()); let (shutdown_sender, mut shutdown_receiver) = channel(()); diff --git a/core/tests/context/fn.rs b/core/tests/context/fn.rs index b80b6876..61fc4801 100644 --- a/core/tests/context/fn.rs +++ b/core/tests/context/fn.rs @@ -111,6 +111,6 @@ fn run_set_func() { assert_eq!(msg, PARAM); }; ctx.set_attribute(KEY, func); - let hyperlane = ctx.get_attribute::<&(dyn Fn(&str) + Send + Sync)>(KEY); + let hyperlane = ctx.get_attribute::<&(dyn Fn(&str) + Send + Sync), &str>(KEY); hyperlane(PARAM); } diff --git a/core/tests/mod.rs b/core/tests/mod.rs index c3fe33b4..59ed11d6 100644 --- a/core/tests/mod.rs +++ b/core/tests/mod.rs @@ -7,6 +7,7 @@ mod server; use hyperlane_core::*; use std::{ + io, net::TcpListener, sync::{Arc, OnceLock}, time::{Duration, Instant}, diff --git a/core/tests/route/fn.rs b/core/tests/route/fn.rs index e73592e5..e38f868f 100644 --- a/core/tests/route/fn.rs +++ b/core/tests/route/fn.rs @@ -3,24 +3,24 @@ use super::*; #[tokio::test] #[should_panic(expected = "EmptyPattern")] async fn empty_route() { - let _server: &Server = Server::default().route::(EMPTY_STR); + let _server: &Server = Server::default().route::(EMPTY_STR); } #[tokio::test] #[should_panic(expected = "DuplicatePattern")] async fn duplicate_route() { let _server: &Server = Server::default() - .route::(ROOT_PATH) - .route::(ROOT_PATH); + .route::(ROOT_PATH) + .route::(ROOT_PATH); } #[test] fn get_route() { let mut server: Server = Server::default(); server - .route::(ROOT_PATH) - .route::("/dynamic/{routing}") - .route::("/regex/{file:^.*$}"); + .route::(ROOT_PATH) + .route::("/dynamic/{routing}") + .route::("/regex/{file:^.*$}"); let route_matcher: RouteMatcher = server.get_route_matcher().clone(); for key in route_matcher.get_static_route().keys() { println!("Static route: {key}"); @@ -40,10 +40,10 @@ fn get_route() { #[test] fn segment_count_optimization() { let mut server: Server = Server::default(); - server.route::("/users/{id}"); - server.route::("/users/{id}/posts"); - server.route::("/users/{id}/posts/{post_id}"); - server.route::("/api/v1/users/{id}"); + server.route::("/users/{id}"); + server.route::("/users/{id}/posts"); + server.route::("/users/{id}/posts/{post_id}"); + server.route::("/api/v1/users/{id}"); let route_matcher: RouteMatcher = server.get_route_matcher().clone(); assert!( route_matcher.get_dynamic_route().contains_key(&2), @@ -65,9 +65,9 @@ fn segment_count_optimization() { #[test] fn regex_route_segment_count() { let mut server: Server = Server::default(); - server.route::("/files/{path:.*}"); - server.route::("/api/{version:\\d+}/users"); - server.route::("/api/{version:\\d+}/posts/{id:\\d+}"); + server.route::("/files/{path:.*}"); + server.route::("/api/{version:\\d+}/users"); + server.route::("/api/{version:\\d+}/posts/{id:\\d+}"); let route_matcher: RouteMatcher = server.get_route_matcher().clone(); assert!( route_matcher.get_regex_route().contains_key(&2), @@ -86,11 +86,11 @@ fn regex_route_segment_count() { #[test] fn mixed_route_types() { let mut server: Server = Server::default(); - server.route::("/"); - server.route::("/about"); - server.route::("/users/{id}"); - server.route::("/posts/{slug}"); - server.route::("/files/{path:.*}"); + server.route::("/"); + server.route::("/about"); + server.route::("/users/{id}"); + server.route::("/posts/{slug}"); + server.route::("/files/{path:.*}"); let route_matcher: RouteMatcher = server.get_route_matcher().clone(); assert_eq!(route_matcher.get_static_route().len(), 2); assert!(route_matcher.get_dynamic_route().contains_key(&2)); @@ -104,7 +104,7 @@ fn large_dynamic_routes() { let start_insert: Instant = Instant::now(); for i in 0..ROUTE_COUNT { let path: String = format!("/api/resource{i}/{{id}}"); - server.route::(&path); + server.route::(&path); } let insert_duration: Duration = start_insert.elapsed(); println!( @@ -137,7 +137,7 @@ fn large_regex_routes() { let start_insert: Instant = Instant::now(); for i in 0..ROUTE_COUNT { let path: String = format!("/api/resource{i}/{{id:[0-9]+}}"); - server.route::(&path); + server.route::(&path); } let insert_duration: Duration = start_insert.elapsed(); println!( @@ -170,7 +170,7 @@ fn large_tail_regex_routes() { let start_insert: Instant = Instant::now(); for i in 0..ROUTE_COUNT { let path: String = format!("/api/resource{i}/{{path:.*}}"); - server.route::(&path); + server.route::(&path); } let insert_duration: Duration = start_insert.elapsed(); println!( diff --git a/core/tests/server/fn.rs b/core/tests/server/fn.rs index 650c6554..72e7d23b 100644 --- a/core/tests/server/fn.rs +++ b/core/tests/server/fn.rs @@ -146,7 +146,7 @@ fn server_send_sync() { #[tokio::test] async fn server_clone_across_threads() { let mut server: Server = Server::default(); - server.route::("/test"); + server.route::("/test"); let server_clone: Server = server.clone(); let handle: JoinHandle<&'static str> = spawn(async move { let _server_in_thread: Server = server_clone; @@ -159,7 +159,7 @@ async fn server_clone_across_threads() { #[tokio::test] async fn server_share_across_threads() { let mut server: Server = Server::default(); - server.route::("/test"); + server.route::("/test"); let server: Arc = Arc::new(server); let server1: Arc = server.clone(); let server2: Arc = server.clone(); @@ -190,12 +190,12 @@ async fn main() { server.request_middleware::(); server.request_middleware::(); server.response_middleware::(); - server.route::("/"); - server.route::("/sse"); - server.route::("/websocket"); - server.route::("/get/all/routes"); - server.route::("/dynamic/{routing}"); - server.route::("/regex/{file:^.*$}"); + server.route::("/"); + server.route::("/sse"); + server.route::("/websocket"); + server.route::("/get/all/routes"); + server.route::("/dynamic/{routing}"); + server.route::("/regex/{file:^.*$}"); let _: Result<(), Server> = SERVER_REF.set(server.clone()); let server_control_hook_1: ServerControlHook = server.run().await.unwrap_or_default(); let server_control_hook_2: ServerControlHook = server_control_hook_1.clone(); @@ -284,7 +284,7 @@ async fn client_format_bind_address_concatenates_host_and_port() { #[tokio::test] async fn client_try_flush_stdout_returns_ok() { - let result: std::io::Result<()> = Server::try_flush_stdout(); + let result: io::Result<()> = Server::try_flush_stdout(); assert!(result.is_ok()); } @@ -295,7 +295,7 @@ async fn client_flush_stdout_does_not_panic() { #[tokio::test] async fn client_try_flush_stderr_returns_ok() { - let result: std::io::Result<()> = Server::try_flush_stderr(); + let result: io::Result<()> = Server::try_flush_stderr(); assert!(result.is_ok()); } @@ -306,7 +306,7 @@ async fn client_flush_stderr_does_not_panic() { #[tokio::test] async fn client_try_flush_stdout_and_stderr_returns_ok() { - let result: std::io::Result<()> = Server::try_flush_stdout_and_stderr(); + let result: io::Result<()> = Server::try_flush_stdout_and_stderr(); assert!(result.is_ok()); } @@ -328,7 +328,7 @@ async fn client_handle_hook_dispatches_to_correct_handler_list() { #[tokio::test] async fn client_route_basic_serial_e2e() { let (control, port) = start_server_with(|register: &mut Server| { - register.route::("/"); + register.route::("/"); }) .await; let request: &[u8] = b"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"; @@ -340,7 +340,7 @@ async fn client_route_basic_serial_e2e() { #[tokio::test] async fn client_dynamic_route_serial_e2e() { let (control, port) = start_server_with(|register: &mut Server| { - register.route::("/dynamic/:id"); + register.route::("/dynamic/:id"); }) .await; let request: &[u8] = @@ -353,7 +353,7 @@ async fn client_dynamic_route_serial_e2e() { #[tokio::test] async fn client_regex_route_serial_e2e() { let (control, port) = start_server_with(|register: &mut Server| { - register.route::("/ws"); + register.route::("/ws"); }) .await; let request: &[u8] = b"GET /ws HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"; @@ -365,7 +365,7 @@ async fn client_regex_route_serial_e2e() { #[tokio::test] async fn client_request_error_404_serial_e2e() { let (control, port) = start_server_with(|register: &mut Server| { - register.route::("/"); + register.route::("/"); register.request_error::(); }) .await; @@ -378,7 +378,7 @@ async fn client_request_error_404_serial_e2e() { #[tokio::test] async fn client_request_middleware_serial_e2e() { let (control, port) = start_server_with(|register: &mut Server| { - register.route::("/"); + register.route::("/"); register.request_middleware::(); }) .await; @@ -391,7 +391,7 @@ async fn client_request_middleware_serial_e2e() { #[tokio::test] async fn client_response_middleware_serial_e2e() { let (control, port) = start_server_with(|register: &mut Server| { - register.route::("/"); + register.route::("/"); register.response_middleware::(); }) .await; @@ -404,7 +404,7 @@ async fn client_response_middleware_serial_e2e() { #[tokio::test] async fn client_task_panic_handler_serial_e2e() { let (control, port) = start_server_with(|register: &mut Server| { - register.route::("/panic/:msg"); + register.route::("/panic/:msg"); register.task_panic::(); }) .await; @@ -418,7 +418,7 @@ async fn client_task_panic_handler_serial_e2e() { #[tokio::test] async fn client_two_servers_on_distinct_ports_serial_e2e() { let (control_a, port_a) = start_server_with(|register: &mut Server| { - register.route::("/"); + register.route::("/"); }) .await; let request: &[u8] = b"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: close\r\n\r\n"; @@ -426,7 +426,7 @@ async fn client_two_servers_on_distinct_ports_serial_e2e() { control_a.shutdown().await; control_a.wait().await; let (control_b, port_b) = start_server_with(|register: &mut Server| { - register.route::("/"); + register.route::("/"); }) .await; assert_ne!(port_a, port_b); @@ -438,11 +438,11 @@ async fn client_two_servers_on_distinct_ports_serial_e2e() { #[tokio::test] async fn client_concurrent_servers_on_distinct_ports_e2e() { let (control_a, port_a) = start_server_with(|register: &mut Server| { - register.route::("/"); + register.route::("/"); }) .await; let (control_b, port_b) = start_server_with(|register: &mut Server| { - register.route::("/"); + register.route::("/"); }) .await; assert_ne!(port_a, port_b); diff --git a/macros/src/common/const.rs b/macros/src/common/const.rs index 9e76d4ec..da3b5439 100644 --- a/macros/src/common/const.rs +++ b/macros/src/common/const.rs @@ -1 +1,254 @@ pub(crate) const SERVER_TYPE_KEY: &str = "Server"; + +/// The crate name segment checked when matching a `Context` parameter type. +pub(crate) const HYPERLANE_CRATE_NAME: &str = "hyperlane"; + +/// The type name segment checked when matching a `Context` parameter type. +pub(crate) const CONTEXT_TYPE_NAME: &str = "Context"; + +/// The type name segment checked when matching a `Stream` parameter type. +pub(crate) const STREAM_TYPE_NAME: &str = "Stream"; + +/// The error message used when a context parameter is not a plain identifier. +pub(crate) const EXPECTED_IDENTIFIER_FOR_CONTEXT_PARAMETER: &str = + "expected identifier for context parameter"; + +/// The error message used when a function signature has no context parameter. +pub(crate) const EXPECTED_CONTEXT_PARAMETER: &str = + "expected at least one parameter of type &::hyperlane::Context"; + +/// The error message used when a stream parameter is not a plain identifier. +pub(crate) const EXPECTED_IDENTIFIER_FOR_STREAM_PARAMETER: &str = + "expected identifier for stream parameter"; + +/// The error message used when a function signature has no stream parameter. +pub(crate) const EXPECTED_STREAM_PARAMETER: &str = + "expected at least one parameter of type &::hyperlane::Stream"; + +/// The error message used when a hook `order` literal is not a valid `isize`. +pub(crate) const CANNOT_PARSE_TO_ISIZE: &str = "Cannot parse to isize"; + +/// The registered name of the `closed` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_CLOSED: &str = "closed"; + +/// The registered name of the `filter` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_FILTER: &str = "filter"; + +/// The registered name of the `try_flush` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_FLUSH: &str = "try_flush"; + +/// The registered name of the `flush` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_FLUSH: &str = "flush"; + +/// The registered name of the `task_panic` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TASK_PANIC: &str = "task_panic"; + +/// The registered name of the `request_error` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_ERROR: &str = "request_error"; + +/// The registered name of the `prologue_hooks` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_PROLOGUE_HOOKS: &str = "prologue_hooks"; + +/// The registered name of the `epilogue_hooks` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_EPILOGUE_HOOKS: &str = "epilogue_hooks"; + +/// The registered name of the `host` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_HOST: &str = "host"; + +/// The registered name of the `reject_host` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REJECT_HOST: &str = "reject_host"; + +/// The registered name of the `hyperlane` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_HYPERLANE: &str = "hyperlane"; + +/// The registered name of the `methods` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_METHODS: &str = "methods"; + +/// The registered name of the `is_get_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_GET_METHOD: &str = "is_get_method"; + +/// The registered name of the `is_post_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_POST_METHOD: &str = "is_post_method"; + +/// The registered name of the `is_put_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_PUT_METHOD: &str = "is_put_method"; + +/// The registered name of the `is_delete_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_DELETE_METHOD: &str = "is_delete_method"; + +/// The registered name of the `is_patch_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_PATCH_METHOD: &str = "is_patch_method"; + +/// The registered name of the `is_head_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_HEAD_METHOD: &str = "is_head_method"; + +/// The registered name of the `is_options_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_OPTIONS_METHOD: &str = "is_options_method"; + +/// The registered name of the `is_connect_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_CONNECT_METHOD: &str = "is_connect_method"; + +/// The registered name of the `is_trace_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_TRACE_METHOD: &str = "is_trace_method"; + +/// The registered name of the `is_unknown_method` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_UNKNOWN_METHOD: &str = "is_unknown_method"; + +/// The registered name of the `referer` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REFERER: &str = "referer"; + +/// The registered name of the `reject_referer` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REJECT_REFERER: &str = "reject_referer"; + +/// The registered name of the `reject` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REJECT: &str = "reject"; + +/// The registered name of the `request_body` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_BODY: &str = "request_body"; + +/// The registered name of the `request_body_json_result` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_BODY_JSON_RESULT: &str = "request_body_json_result"; + +/// The registered name of the `request_body_json` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_BODY_JSON: &str = "request_body_json"; + +/// The registered name of the `try_get_attribute` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_ATTRIBUTE: &str = "try_get_attribute"; + +/// The registered name of the `attribute` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_ATTRIBUTE: &str = "attribute"; + +/// The registered name of the `attributes` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_ATTRIBUTES: &str = "attributes"; + +/// The registered name of the `try_get_task_panic_data` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_TASK_PANIC_DATA: &str = "try_get_task_panic_data"; + +/// The registered name of the `task_panic_data` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TASK_PANIC_DATA: &str = "task_panic_data"; + +/// The registered name of the `try_get_request_error_data` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_REQUEST_ERROR_DATA: &str = + "try_get_request_error_data"; + +/// The registered name of the `request_error_data` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_ERROR_DATA: &str = "request_error_data"; + +/// The registered name of the `try_get_route_param` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_ROUTE_PARAM: &str = "try_get_route_param"; + +/// The registered name of the `route_param` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_ROUTE_PARAM: &str = "route_param"; + +/// The registered name of the `route_params` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_ROUTE_PARAMS: &str = "route_params"; + +/// The registered name of the `try_get_request_query` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_REQUEST_QUERY: &str = "try_get_request_query"; + +/// The registered name of the `request_query` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_QUERY: &str = "request_query"; + +/// The registered name of the `request_querys` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_QUERYS: &str = "request_querys"; + +/// The registered name of the `try_get_request_header` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_REQUEST_HEADER: &str = "try_get_request_header"; + +/// The registered name of the `request_header` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_HEADER: &str = "request_header"; + +/// The registered name of the `request_headers` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_HEADERS: &str = "request_headers"; + +/// The registered name of the `try_get_request_cookie` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_REQUEST_COOKIE: &str = "try_get_request_cookie"; + +/// The registered name of the `request_cookie` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_COOKIE: &str = "request_cookie"; + +/// The registered name of the `request_cookies` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_COOKIES: &str = "request_cookies"; + +/// The registered name of the `request_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_VERSION: &str = "request_version"; + +/// The registered name of the `request_path` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_PATH: &str = "request_path"; + +/// The registered name of the `request_middleware` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_REQUEST_MIDDLEWARE: &str = "request_middleware"; + +/// The registered name of the `response_status_code` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_RESPONSE_STATUS_CODE: &str = "response_status_code"; + +/// The registered name of the `response_reason_phrase` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_RESPONSE_REASON_PHRASE: &str = "response_reason_phrase"; + +/// The registered name of the `response_header` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_RESPONSE_HEADER: &str = "response_header"; + +/// The registered name of the `response_body` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_RESPONSE_BODY: &str = "response_body"; + +/// The registered name of the `clear_response_headers` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_CLEAR_RESPONSE_HEADERS: &str = "clear_response_headers"; + +/// The registered name of the `response_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_RESPONSE_VERSION: &str = "response_version"; + +/// The registered name of the `response_middleware` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_RESPONSE_MIDDLEWARE: &str = "response_middleware"; + +/// The registered name of the `route` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_ROUTE: &str = "route"; + +/// The registered name of the `try_send` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_SEND: &str = "try_send"; + +/// The registered name of the `send` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_SEND: &str = "send"; + +/// The registered name of the `try_get_http_request` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_HTTP_REQUEST: &str = "try_get_http_request"; + +/// The registered name of the `try_get_websocket_request` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_TRY_GET_WEBSOCKET_REQUEST: &str = + "try_get_websocket_request"; + +/// The registered name of the `is_ws_upgrade_type` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_WS_UPGRADE_TYPE: &str = "is_ws_upgrade_type"; + +/// The registered name of the `is_h2c_upgrade_type` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_H2C_UPGRADE_TYPE: &str = "is_h2c_upgrade_type"; + +/// The registered name of the `is_tls_upgrade_type` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_TLS_UPGRADE_TYPE: &str = "is_tls_upgrade_type"; + +/// The registered name of the `is_unknown_upgrade_type` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_UNKNOWN_UPGRADE_TYPE: &str = "is_unknown_upgrade_type"; + +/// The registered name of the `is_http0_9_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_HTTP0_9_VERSION: &str = "is_http0_9_version"; + +/// The registered name of the `is_http1_0_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_HTTP1_0_VERSION: &str = "is_http1_0_version"; + +/// The registered name of the `is_http1_1_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_HTTP1_1_VERSION: &str = "is_http1_1_version"; + +/// The registered name of the `is_http2_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_HTTP2_VERSION: &str = "is_http2_version"; + +/// The registered name of the `is_http3_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_HTTP3_VERSION: &str = "is_http3_version"; + +/// The registered name of the `is_http1_1_or_higher_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_HTTP1_1_OR_HIGHER_VERSION: &str = + "is_http1_1_or_higher_version"; + +/// The registered name of the `is_http_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_HTTP_VERSION: &str = "is_http_version"; + +/// The registered name of the `is_unknown_version` injectable macro. +pub(crate) const INJECTABLE_MACRO_NAME_IS_UNKNOWN_VERSION: &str = "is_unknown_version"; diff --git a/macros/src/common/fn.rs b/macros/src/common/fn.rs index fc9d8151..320e475d 100644 --- a/macros/src/common/fn.rs +++ b/macros/src/common/fn.rs @@ -5,15 +5,15 @@ use super::*; /// # Arguments /// /// - `TokenStream` - The input token stream to process. -/// - `FnOnce(&Ident, &Ident) -> TokenStream2` - Function to generate code inserted before, receiving context and stream idents. +/// - `F` - Function to generate code inserted before, receiving context and stream idents. /// /// # Returns /// /// - `TokenStream` - The expanded token stream with inserted code. -fn inject_at_start( - input: TokenStream, - before_fn: impl FnOnce(&Ident, &Ident) -> proc_macro2::TokenStream, -) -> TokenStream { +fn inject_at_start(input: TokenStream, before_fn: F) -> TokenStream +where + F: FnOnce(&Ident, &Ident) -> proc_macro2::TokenStream, +{ let input_fn: ItemFn = parse_macro_input!(input as ItemFn); let vis: &Visibility = &input_fn.vis; let sig: &Signature = &input_fn.sig; @@ -44,11 +44,15 @@ fn inject_at_start( /// # Arguments /// /// - `TokenStream` - The input `TokenStream` to process. -/// - `FnOnce(&Ident, &Ident) -> TokenStream2` - A closure that takes context and stream identifiers and returns a `TokenStream` to be inserted at the end of the method. -fn inject_at_end( - input: TokenStream, - after_fn: impl FnOnce(&Ident, &Ident) -> proc_macro2::TokenStream, -) -> TokenStream { +/// - `F` - A closure that takes context and stream identifiers and returns a `TokenStream` to be inserted at the end of the method. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream with inserted code. +fn inject_at_end(input: TokenStream, after_fn: F) -> TokenStream +where + F: FnOnce(&Ident, &Ident) -> proc_macro2::TokenStream, +{ let input_fn: ItemFn = parse_macro_input!(input as ItemFn); let vis: &Visibility = &input_fn.vis; let sig: &Signature = &input_fn.sig; @@ -105,16 +109,15 @@ fn inject_at_end( /// /// - `Position` - The position at which to inject the code (`Prologue` or `Epilogue`). /// - `TokenStream` - The input `TokenStream` of the method to modify. -/// - `FnOnce(&Ident, &Ident) -> TokenStream2` - A closure that generates the code to be injected, based on the method's context and stream identifiers. +/// - `F` - A closure that generates the code to be injected, based on the method's context and stream identifiers. /// /// # Returns /// /// - `TokenStream` - Returns the modified `TokenStream` with the injected code. -pub(crate) fn inject( - position: Position, - input: TokenStream, - hook: impl FnOnce(&Ident, &Ident) -> proc_macro2::TokenStream, -) -> TokenStream { +pub(crate) fn inject(position: Position, input: TokenStream, hook: F) -> TokenStream +where + F: FnOnce(&Ident, &Ident) -> proc_macro2::TokenStream, +{ match position { Position::Prologue => inject_at_start(input, hook), Position::Epilogue => inject_at_end(input, hook), @@ -141,12 +144,14 @@ fn is_context_type(ty: &Type) -> bool { let segments: Vec<&syn::PathSegment> = path.segments.iter().collect(); if segments.len() >= 2 { let last_two: &[&PathSegment] = &segments[segments.len() - 2..]; - if last_two[0].ident == "hyperlane" && last_two[1].ident == "Context" { + if last_two[0].ident == HYPERLANE_CRATE_NAME + && last_two[1].ident == CONTEXT_TYPE_NAME + { return true; } } } - if path.segments.len() == 1 && path.segments[0].ident == "Context" { + if path.segments.len() == 1 && path.segments[0].ident == CONTEXT_TYPE_NAME { return true; } } @@ -173,12 +178,14 @@ fn is_stream_type(ty: &Type) -> bool { let segments: Vec<&syn::PathSegment> = path.segments.iter().collect(); if segments.len() >= 2 { let last_two: &[&PathSegment] = &segments[segments.len() - 2..]; - if last_two[0].ident == "hyperlane" && last_two[1].ident == "Stream" { + if last_two[0].ident == HYPERLANE_CRATE_NAME + && last_two[1].ident == STREAM_TYPE_NAME + { return true; } } } - if path.segments.len() == 1 && path.segments[0].ident == "Stream" { + if path.segments.len() == 1 && path.segments[0].ident == STREAM_TYPE_NAME { return true; } } @@ -212,7 +219,7 @@ pub(crate) fn parse_context_from_signature(sig: &Signature) -> syn::Result { return Err(syn::Error::new_spanned( &pat_type.pat, - "expected identifier for context parameter", + EXPECTED_IDENTIFIER_FOR_CONTEXT_PARAMETER, )); } }; @@ -221,7 +228,7 @@ pub(crate) fn parse_context_from_signature(sig: &Signature) -> syn::Result syn::Result _ => { return Err(syn::Error::new_spanned( &pat_type.pat, - "expected identifier for stream parameter", + EXPECTED_IDENTIFIER_FOR_STREAM_PARAMETER, )); } }; @@ -261,7 +268,7 @@ pub(crate) fn parse_stream_from_signature(sig: &Signature) -> syn::Result } Err(syn::Error::new_spanned( &sig.inputs, - "expected at least one parameter of type &::hyperlane::Stream", + EXPECTED_STREAM_PARAMETER, )) } @@ -279,7 +286,7 @@ pub(crate) fn parse_stream_from_signature(sig: &Signature) -> syn::Result /// /// # Returns /// -/// - `TokenStream` - A `TokenStream2` representing `Some(isize)` for supported literals, or `None` otherwise. +/// - `proc_macro2::TokenStream` - A token stream representing `Some(isize)` for supported literals, or `None` otherwise. pub(crate) fn expr_to_isize(opt_expr: &Option) -> proc_macro2::TokenStream { match opt_expr { Some(expr) => match expr { @@ -294,7 +301,7 @@ pub(crate) fn expr_to_isize(opt_expr: &Option) -> proc_macro2::TokenStream lit: Lit::Str(lit_str), .. }) => { - let value: isize = lit_str.value().parse().expect("Cannot parse to isize"); + let value: isize = lit_str.value().parse().expect(CANNOT_PARSE_TO_ISIZE); quote! { Some(#value) } } _ => quote! { None }, @@ -312,7 +319,7 @@ pub(crate) fn expr_to_isize(opt_expr: &Option) -> proc_macro2::TokenStream /// /// # Returns /// -/// - `TokenStream2` - The token stream calling `#context.leak_mut()`. +/// - `proc_macro2::TokenStream` - The token stream calling `#context.leak_mut()`. /// /// # Safety /// @@ -334,11 +341,12 @@ pub(crate) fn leak_mut_context(is_unsafe_error: bool, context: &Ident) -> proc_m /// /// # Arguments /// +/// - `bool` - Whether to use `unsafe` or not. /// - `&Ident` - The context variable identifier. /// /// # Returns /// -/// - `TokenStream2` - The token stream calling `#context.leak()`. +/// - `proc_macro2::TokenStream` - The token stream calling `#context.leak()`. /// /// # Safety /// diff --git a/macros/src/common/impl.rs b/macros/src/common/impl.rs index 56633a42..03489b22 100644 --- a/macros/src/common/impl.rs +++ b/macros/src/common/impl.rs @@ -10,7 +10,7 @@ impl Parse for OrderAttr { /// /// # Arguments /// - /// - `input` - The token stream to parse. + /// - `ParseStream` - The token stream to parse. /// /// # Returns /// diff --git a/macros/src/common/static.rs b/macros/src/common/static.rs index a5e10d05..7bbd6e09 100644 --- a/macros/src/common/static.rs +++ b/macros/src/common/static.rs @@ -5,299 +5,299 @@ use super::*; /// This array contains all the macro handlers that can be injected using the `inject` macro. pub(crate) static INJECTABLE_MACROS: &[InjectableMacro] = &[ InjectableMacro { - name: "closed", + name: INJECTABLE_MACRO_NAME_CLOSED, handler: Handler::NoAttrPosition(closed_macro), }, InjectableMacro { - name: "filter", + name: INJECTABLE_MACRO_NAME_FILTER, handler: Handler::WithAttrPosition(filter_macro), }, InjectableMacro { - name: "try_flush", + name: INJECTABLE_MACRO_NAME_TRY_FLUSH, handler: Handler::NoAttrPosition(try_flush_macro), }, InjectableMacro { - name: "flush", + name: INJECTABLE_MACRO_NAME_FLUSH, handler: Handler::NoAttrPosition(flush_macro), }, InjectableMacro { - name: "task_panic", + name: INJECTABLE_MACRO_NAME_TASK_PANIC, handler: Handler::WithAttr(task_panic_macro), }, InjectableMacro { - name: "request_error", + name: INJECTABLE_MACRO_NAME_REQUEST_ERROR, handler: Handler::WithAttr(request_error_macro), }, InjectableMacro { - name: "prologue_hooks", + name: INJECTABLE_MACRO_NAME_PROLOGUE_HOOKS, handler: Handler::WithAttrPosition(prologue_hooks_macro), }, InjectableMacro { - name: "epilogue_hooks", + name: INJECTABLE_MACRO_NAME_EPILOGUE_HOOKS, handler: Handler::WithAttrPosition(epilogue_hooks_macro), }, InjectableMacro { - name: "host", + name: INJECTABLE_MACRO_NAME_HOST, handler: Handler::WithAttrPosition(host_macro), }, InjectableMacro { - name: "reject_host", + name: INJECTABLE_MACRO_NAME_REJECT_HOST, handler: Handler::WithAttrPosition(reject_host_macro), }, InjectableMacro { - name: "hyperlane", + name: INJECTABLE_MACRO_NAME_HYPERLANE, handler: Handler::WithAttr(hyperlane_macro), }, InjectableMacro { - name: "methods", + name: INJECTABLE_MACRO_NAME_METHODS, handler: Handler::WithAttrPosition(methods_macro), }, InjectableMacro { - name: "is_get_method", + name: INJECTABLE_MACRO_NAME_IS_GET_METHOD, handler: Handler::NoAttrPosition(is_get_method_handler), }, InjectableMacro { - name: "is_post_method", + name: INJECTABLE_MACRO_NAME_IS_POST_METHOD, handler: Handler::NoAttrPosition(is_post_method_handler), }, InjectableMacro { - name: "is_put_method", + name: INJECTABLE_MACRO_NAME_IS_PUT_METHOD, handler: Handler::NoAttrPosition(is_put_method_handler), }, InjectableMacro { - name: "is_delete_method", + name: INJECTABLE_MACRO_NAME_IS_DELETE_METHOD, handler: Handler::NoAttrPosition(is_delete_method_handler), }, InjectableMacro { - name: "is_patch_method", + name: INJECTABLE_MACRO_NAME_IS_PATCH_METHOD, handler: Handler::NoAttrPosition(is_patch_method_handler), }, InjectableMacro { - name: "is_head_method", + name: INJECTABLE_MACRO_NAME_IS_HEAD_METHOD, handler: Handler::NoAttrPosition(is_head_method_handler), }, InjectableMacro { - name: "is_options_method", + name: INJECTABLE_MACRO_NAME_IS_OPTIONS_METHOD, handler: Handler::NoAttrPosition(is_options_method_handler), }, InjectableMacro { - name: "is_connect_method", + name: INJECTABLE_MACRO_NAME_IS_CONNECT_METHOD, handler: Handler::NoAttrPosition(is_connect_method_handler), }, InjectableMacro { - name: "is_trace_method", + name: INJECTABLE_MACRO_NAME_IS_TRACE_METHOD, handler: Handler::NoAttrPosition(is_trace_method_handler), }, InjectableMacro { - name: "is_unknown_method", + name: INJECTABLE_MACRO_NAME_IS_UNKNOWN_METHOD, handler: Handler::NoAttrPosition(is_unknown_method_handler), }, InjectableMacro { - name: "referer", + name: INJECTABLE_MACRO_NAME_REFERER, handler: Handler::WithAttrPosition(referer_macro), }, InjectableMacro { - name: "reject_referer", + name: INJECTABLE_MACRO_NAME_REJECT_REFERER, handler: Handler::WithAttrPosition(reject_referer_macro), }, InjectableMacro { - name: "reject", + name: INJECTABLE_MACRO_NAME_REJECT, handler: Handler::WithAttrPosition(reject_macro), }, InjectableMacro { - name: "request_body", + name: INJECTABLE_MACRO_NAME_REQUEST_BODY, handler: Handler::WithAttrPosition(request_body_macro), }, InjectableMacro { - name: "request_body_json_result", + name: INJECTABLE_MACRO_NAME_REQUEST_BODY_JSON_RESULT, handler: Handler::WithAttrPosition(request_body_json_result_macro), }, InjectableMacro { - name: "request_body_json", + name: INJECTABLE_MACRO_NAME_REQUEST_BODY_JSON, handler: Handler::WithAttrPosition(request_body_json_macro), }, InjectableMacro { - name: "try_get_attribute", + name: INJECTABLE_MACRO_NAME_TRY_GET_ATTRIBUTE, handler: Handler::WithAttrPosition(try_get_attribute_macro), }, InjectableMacro { - name: "attribute", + name: INJECTABLE_MACRO_NAME_ATTRIBUTE, handler: Handler::WithAttrPosition(attribute_macro), }, InjectableMacro { - name: "attributes", + name: INJECTABLE_MACRO_NAME_ATTRIBUTES, handler: Handler::WithAttrPosition(attributes_macro), }, InjectableMacro { - name: "try_get_task_panic_data", + name: INJECTABLE_MACRO_NAME_TRY_GET_TASK_PANIC_DATA, handler: Handler::WithAttrPosition(try_get_task_panic_data_macro), }, InjectableMacro { - name: "task_panic_data", + name: INJECTABLE_MACRO_NAME_TASK_PANIC_DATA, handler: Handler::WithAttrPosition(task_panic_data_macro), }, InjectableMacro { - name: "try_get_request_error_data", + name: INJECTABLE_MACRO_NAME_TRY_GET_REQUEST_ERROR_DATA, handler: Handler::WithAttrPosition(try_get_request_error_data_macro), }, InjectableMacro { - name: "request_error_data", + name: INJECTABLE_MACRO_NAME_REQUEST_ERROR_DATA, handler: Handler::WithAttrPosition(request_error_data_macro), }, InjectableMacro { - name: "try_get_route_param", + name: INJECTABLE_MACRO_NAME_TRY_GET_ROUTE_PARAM, handler: Handler::WithAttrPosition(try_get_route_param_macro), }, InjectableMacro { - name: "route_param", + name: INJECTABLE_MACRO_NAME_ROUTE_PARAM, handler: Handler::WithAttrPosition(route_param_macro), }, InjectableMacro { - name: "route_params", + name: INJECTABLE_MACRO_NAME_ROUTE_PARAMS, handler: Handler::WithAttrPosition(route_params_macro), }, InjectableMacro { - name: "try_get_request_query", + name: INJECTABLE_MACRO_NAME_TRY_GET_REQUEST_QUERY, handler: Handler::WithAttrPosition(try_get_request_query_macro), }, InjectableMacro { - name: "request_query", + name: INJECTABLE_MACRO_NAME_REQUEST_QUERY, handler: Handler::WithAttrPosition(request_query_macro), }, InjectableMacro { - name: "request_querys", + name: INJECTABLE_MACRO_NAME_REQUEST_QUERYS, handler: Handler::WithAttrPosition(request_querys_macro), }, InjectableMacro { - name: "try_get_request_header", + name: INJECTABLE_MACRO_NAME_TRY_GET_REQUEST_HEADER, handler: Handler::WithAttrPosition(try_get_request_header_macro), }, InjectableMacro { - name: "request_header", + name: INJECTABLE_MACRO_NAME_REQUEST_HEADER, handler: Handler::WithAttrPosition(request_header_macro), }, InjectableMacro { - name: "request_headers", + name: INJECTABLE_MACRO_NAME_REQUEST_HEADERS, handler: Handler::WithAttrPosition(request_headers_macro), }, InjectableMacro { - name: "try_get_request_cookie", + name: INJECTABLE_MACRO_NAME_TRY_GET_REQUEST_COOKIE, handler: Handler::WithAttrPosition(try_get_request_cookie_macro), }, InjectableMacro { - name: "request_cookie", + name: INJECTABLE_MACRO_NAME_REQUEST_COOKIE, handler: Handler::WithAttrPosition(request_cookie_macro), }, InjectableMacro { - name: "request_cookies", + name: INJECTABLE_MACRO_NAME_REQUEST_COOKIES, handler: Handler::WithAttrPosition(request_cookies_macro), }, InjectableMacro { - name: "request_version", + name: INJECTABLE_MACRO_NAME_REQUEST_VERSION, handler: Handler::WithAttrPosition(request_version_macro), }, InjectableMacro { - name: "request_path", + name: INJECTABLE_MACRO_NAME_REQUEST_PATH, handler: Handler::WithAttrPosition(request_path_macro), }, InjectableMacro { - name: "request_middleware", + name: INJECTABLE_MACRO_NAME_REQUEST_MIDDLEWARE, handler: Handler::WithAttr(request_middleware_macro), }, InjectableMacro { - name: "response_status_code", + name: INJECTABLE_MACRO_NAME_RESPONSE_STATUS_CODE, handler: Handler::WithAttrPosition(response_status_code_macro), }, InjectableMacro { - name: "response_reason_phrase", + name: INJECTABLE_MACRO_NAME_RESPONSE_REASON_PHRASE, handler: Handler::WithAttrPosition(response_reason_phrase_macro), }, InjectableMacro { - name: "response_header", + name: INJECTABLE_MACRO_NAME_RESPONSE_HEADER, handler: Handler::WithAttrPosition(response_header_macro), }, InjectableMacro { - name: "response_body", + name: INJECTABLE_MACRO_NAME_RESPONSE_BODY, handler: Handler::WithAttrPosition(response_body_macro), }, InjectableMacro { - name: "clear_response_headers", + name: INJECTABLE_MACRO_NAME_CLEAR_RESPONSE_HEADERS, handler: Handler::NoAttrPosition(clear_response_headers_macro), }, InjectableMacro { - name: "response_version", + name: INJECTABLE_MACRO_NAME_RESPONSE_VERSION, handler: Handler::WithAttrPosition(response_version_macro), }, InjectableMacro { - name: "response_middleware", + name: INJECTABLE_MACRO_NAME_RESPONSE_MIDDLEWARE, handler: Handler::WithAttr(response_middleware_macro), }, InjectableMacro { - name: "route", + name: INJECTABLE_MACRO_NAME_ROUTE, handler: Handler::WithAttr(route_macro), }, InjectableMacro { - name: "try_send", + name: INJECTABLE_MACRO_NAME_TRY_SEND, handler: Handler::WithAttrPosition(try_send_macro), }, InjectableMacro { - name: "send", + name: INJECTABLE_MACRO_NAME_SEND, handler: Handler::WithAttrPosition(send_macro), }, InjectableMacro { - name: "try_get_http_request", + name: INJECTABLE_MACRO_NAME_TRY_GET_HTTP_REQUEST, handler: Handler::WithAttr(try_get_http_request_macro), }, InjectableMacro { - name: "try_get_websocket_request", + name: INJECTABLE_MACRO_NAME_TRY_GET_WEBSOCKET_REQUEST, handler: Handler::WithAttr(try_get_websocket_request_macro), }, InjectableMacro { - name: "is_ws_upgrade_type", + name: INJECTABLE_MACRO_NAME_IS_WS_UPGRADE_TYPE, handler: Handler::NoAttrPosition(is_ws_upgrade_type_macro), }, InjectableMacro { - name: "is_h2c_upgrade_type", + name: INJECTABLE_MACRO_NAME_IS_H2C_UPGRADE_TYPE, handler: Handler::NoAttrPosition(is_h2c_upgrade_type_macro), }, InjectableMacro { - name: "is_tls_upgrade_type", + name: INJECTABLE_MACRO_NAME_IS_TLS_UPGRADE_TYPE, handler: Handler::NoAttrPosition(is_tls_upgrade_type_macro), }, InjectableMacro { - name: "is_unknown_upgrade_type", + name: INJECTABLE_MACRO_NAME_IS_UNKNOWN_UPGRADE_TYPE, handler: Handler::NoAttrPosition(is_unknown_upgrade_type_macro), }, InjectableMacro { - name: "is_http0_9_version", + name: INJECTABLE_MACRO_NAME_IS_HTTP0_9_VERSION, handler: Handler::NoAttrPosition(is_http0_9_version_macro), }, InjectableMacro { - name: "is_http1_0_version", + name: INJECTABLE_MACRO_NAME_IS_HTTP1_0_VERSION, handler: Handler::NoAttrPosition(is_http1_0_version_macro), }, InjectableMacro { - name: "is_http1_1_version", + name: INJECTABLE_MACRO_NAME_IS_HTTP1_1_VERSION, handler: Handler::NoAttrPosition(is_http1_1_version_macro), }, InjectableMacro { - name: "is_http2_version", + name: INJECTABLE_MACRO_NAME_IS_HTTP2_VERSION, handler: Handler::NoAttrPosition(is_http2_version_macro), }, InjectableMacro { - name: "is_http3_version", + name: INJECTABLE_MACRO_NAME_IS_HTTP3_VERSION, handler: Handler::NoAttrPosition(is_http3_version_macro), }, InjectableMacro { - name: "is_http1_1_or_higher_version", + name: INJECTABLE_MACRO_NAME_IS_HTTP1_1_OR_HIGHER_VERSION, handler: Handler::NoAttrPosition(is_http1_1_or_higher_version_macro), }, InjectableMacro { - name: "is_http_version", + name: INJECTABLE_MACRO_NAME_IS_HTTP_VERSION, handler: Handler::NoAttrPosition(is_http_version_macro), }, InjectableMacro { - name: "is_unknown_version", + name: INJECTABLE_MACRO_NAME_IS_UNKNOWN_VERSION, handler: Handler::NoAttrPosition(is_unknown_version_macro), }, ]; diff --git a/macros/src/context/fn.rs b/macros/src/context/fn.rs index 99c20490..b9dc4271 100644 --- a/macros/src/context/fn.rs +++ b/macros/src/context/fn.rs @@ -3,6 +3,14 @@ use super::*; /// Checks if a type is a mutable reference type. /// /// Returns true if the type is `&mut T`, false otherwise (including `&T` and other types). +/// +/// # Arguments +/// +/// - `&Type` - The type to inspect for mutable reference syntax. +/// +/// # Returns +/// +/// - `bool` - Whether the type is a mutable reference. pub(crate) fn is_mutable_reference_type(ty: &Type) -> bool { if let Type::Reference(type_ref) = ty { type_ref.mutability.is_some() diff --git a/macros/src/context/impl.rs b/macros/src/context/impl.rs index 3b98a852..839cee6d 100644 --- a/macros/src/context/impl.rs +++ b/macros/src/context/impl.rs @@ -8,6 +8,15 @@ use super::*; /// /// Expected format: `ctx` or `ctx: &mut Context` or `ctx: &Context` impl Parse for ContextInput { + /// Parses the input token stream into a ContextInput structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `Result` - The parsed ContextInput, or an error for invalid input. fn parse(input: ParseStream) -> Result { let source_ctx: Ident = input.parse()?; let ty: Option = if input.peek(Token![:]) { diff --git a/macros/src/from_stream/const.rs b/macros/src/from_stream/const.rs new file mode 100644 index 00000000..cc359c86 --- /dev/null +++ b/macros/src/from_stream/const.rs @@ -0,0 +1,2 @@ +/// The error message used when more than one parameter is provided to a stream attribute. +pub(crate) const EXPECTED_AT_MOST_ONE_PARAMETER: &str = "expected at most one parameter"; diff --git a/macros/src/from_stream/impl.rs b/macros/src/from_stream/impl.rs index fa4d9c19..a2bcad2a 100644 --- a/macros/src/from_stream/impl.rs +++ b/macros/src/from_stream/impl.rs @@ -24,12 +24,14 @@ impl Parse for FromStreamData { /// 2. No parameters: variable_name will be None /// /// # Arguments + /// /// - `ParseStream` - The ParseStream containing the token stream to be parsed /// /// # Returns - /// Returns `syn::Result` where: - /// - Ok(FromStreamData) contains the successfully parsed data with variable name - /// - Err(syn::Error) contains an appropriate error message for invalid input + /// + /// - `syn::Result` - `Ok(FromStreamData)` contains the successfully parsed data + /// with variable name, `Err(syn::Error)` contains an appropriate error message + /// for invalid input. /// /// # Errors /// The function returns errors in the following cases: @@ -42,7 +44,7 @@ impl Parse for FromStreamData { if !input.is_empty() { return Err(syn::Error::new( input.span(), - "expected at most one parameter", + EXPECTED_AT_MOST_ONE_PARAMETER, )); } Some(expr) diff --git a/macros/src/from_stream/mod.rs b/macros/src/from_stream/mod.rs index 5a079a81..57e01e7c 100644 --- a/macros/src/from_stream/mod.rs +++ b/macros/src/from_stream/mod.rs @@ -1,6 +1,7 @@ +mod r#const; mod r#impl; mod r#struct; -pub(crate) use r#struct::*; +pub(crate) use {r#const::*, r#struct::*}; use super::*; diff --git a/macros/src/hook/fn.rs b/macros/src/hook/fn.rs index d0df3d42..1e0061a7 100644 --- a/macros/src/hook/fn.rs +++ b/macros/src/hook/fn.rs @@ -83,11 +83,14 @@ pub(crate) fn prologue_hooks_macro( position, item, |context: &syn::Ident, stream: &syn::Ident| { - let hook_calls = functions.iter().map(|function_expr: &syn::Expr| { - quote! { - let _ = #function_expr(#stream, #context).await; - } - }); + let hook_calls: Vec = functions + .iter() + .map(|function_expr: &syn::Expr| { + quote! { + let _ = #function_expr(#stream, #context).await; + } + }) + .collect(); quote! { #(#hook_calls)* } @@ -117,11 +120,14 @@ pub(crate) fn epilogue_hooks_macro( position, item, |context: &syn::Ident, stream: &syn::Ident| { - let hook_calls = functions.iter().map(|function_expr: &syn::Expr| { - quote! { - let _ = #function_expr(#stream, #context).await; - } - }); + let hook_calls: Vec = functions + .iter() + .map(|function_expr: &syn::Expr| { + quote! { + let _ = #function_expr(#stream, #context).await; + } + }) + .collect(); quote! { #(#hook_calls)* } diff --git a/macros/src/host/fn.rs b/macros/src/host/fn.rs index 833dc712..996346b9 100644 --- a/macros/src/host/fn.rs +++ b/macros/src/host/fn.rs @@ -15,13 +15,17 @@ use super::*; pub(crate) fn host_macro(attr: TokenStream, item: TokenStream, position: Position) -> TokenStream { let multi_host: MultiHostData = parse_macro_input!(attr as MultiHostData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_host.host_values.iter().map(|host_value: &syn::Expr| { - quote! { - if #context.get_request().get_host() != #host_value { - return ::hyperlane::Status::Continue; + let statements: Vec = multi_host + .host_values + .iter() + .map(|host_value: &syn::Expr| { + quote! { + if #context.get_request().get_host() != #host_value { + return ::hyperlane::Status::Continue; + } } - } - }); + }) + .collect(); quote! { #(#statements)* } @@ -47,13 +51,17 @@ pub(crate) fn reject_host_macro( ) -> TokenStream { let multi_host: MultiHostData = parse_macro_input!(attr as MultiHostData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_host.host_values.iter().map(|host_value: &syn::Expr| { - quote! { - if #context.get_request().get_host() == #host_value { - return ::hyperlane::Status::Continue; + let statements: Vec = multi_host + .host_values + .iter() + .map(|host_value: &syn::Expr| { + quote! { + if #context.get_request().get_host() == #host_value { + return ::hyperlane::Status::Continue; + } } - } - }); + }) + .collect(); quote! { #(#statements)* } diff --git a/macros/src/host/impl.rs b/macros/src/host/impl.rs index 542ab811..022d1363 100644 --- a/macros/src/host/impl.rs +++ b/macros/src/host/impl.rs @@ -13,6 +13,15 @@ use super::*; /// /// - `syn::Result` - Parsed MultiHostData or error. impl Parse for MultiHostData { + /// Parses the input token stream into a MultiHostData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiHostData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut host_values: Vec = Vec::new(); loop { diff --git a/macros/src/hyperlane/impl.rs b/macros/src/hyperlane/impl.rs index 7b206bc9..68dbc4b8 100644 --- a/macros/src/hyperlane/impl.rs +++ b/macros/src/hyperlane/impl.rs @@ -14,6 +14,15 @@ use super::*; /// /// A `syn::Result` containing the parsed `MultiHyperlaneAttr` or an error. impl Parse for MultiHyperlaneAttr { + /// Parses the input token stream into a MultiHyperlaneAttr structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `Result` - The parsed MultiHyperlaneAttr, or an error for invalid input. fn parse(input: ParseStream) -> Result { let mut params: Vec<(Ident, Ident)> = Vec::new(); loop { diff --git a/macros/src/inject/const.rs b/macros/src/inject/const.rs new file mode 100644 index 00000000..10b855f1 --- /dev/null +++ b/macros/src/inject/const.rs @@ -0,0 +1,5 @@ +/// The error message used when a macro attribute path is not a plain identifier. +pub(crate) const MACRO_PATH_SHOULD_HAVE_IDENTIFIER: &str = "Macro path should have an identifier"; + +/// The error message used when the `#[prologue_macros]` attribute list cannot be parsed. +pub(crate) const FAILED_TO_PARSE_MACRO_ATTRIBUTES: &str = "Failed to parse macro attributes"; diff --git a/macros/src/inject/fn.rs b/macros/src/inject/fn.rs index cc46e438..33b48583 100644 --- a/macros/src/inject/fn.rs +++ b/macros/src/inject/fn.rs @@ -23,7 +23,7 @@ fn apply_macro(macro_meta: &Meta, item_stream: TokenStream, position: Position) let (macro_name, macro_attr) = match macro_meta { Meta::Path(path) => ( path.get_ident() - .expect("Macro path should have an identifier") + .expect(MACRO_PATH_SHOULD_HAVE_IDENTIFIER) .to_string(), TokenStream::new(), ), @@ -31,7 +31,7 @@ fn apply_macro(macro_meta: &Meta, item_stream: TokenStream, position: Position) meta_list .path .get_ident() - .expect("Macro path should have an identifier") + .expect(MACRO_PATH_SHOULD_HAVE_IDENTIFIER) .to_string(), meta_list.tokens.clone().into(), ), @@ -70,7 +70,7 @@ fn apply_macro(macro_meta: &Meta, item_stream: TokenStream, position: Position) pub(crate) fn prologue_macros_macro(attr: TokenStream, item: TokenStream) -> TokenStream { let metas: Punctuated = Punctuated::::parse_terminated .parse(attr) - .expect("Failed to parse macro attributes"); + .expect(FAILED_TO_PARSE_MACRO_ATTRIBUTES); let mut current_stream: TokenStream = item; for meta in metas.iter().rev() { current_stream = apply_macro(meta, current_stream, Position::Prologue); @@ -94,7 +94,7 @@ pub(crate) fn prologue_macros_macro(attr: TokenStream, item: TokenStream) -> Tok pub(crate) fn epilogue_macros_macro(attr: TokenStream, item: TokenStream) -> TokenStream { let metas: Punctuated = Punctuated::::parse_terminated .parse(attr) - .expect("Failed to parse macro attributes"); + .expect(FAILED_TO_PARSE_MACRO_ATTRIBUTES); let mut current_stream: TokenStream = item; for meta in metas.iter() { current_stream = apply_macro(meta, current_stream, Position::Epilogue); diff --git a/macros/src/inject/mod.rs b/macros/src/inject/mod.rs index 910cac30..09524dbe 100644 --- a/macros/src/inject/mod.rs +++ b/macros/src/inject/mod.rs @@ -1,5 +1,6 @@ +mod r#const; mod r#fn; -pub(crate) use r#fn::*; +pub(crate) use {r#const::*, r#fn::*}; use super::*; diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 509e8b5f..74b68323 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -220,6 +220,15 @@ pub fn try_get_http_request(attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_get_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_get_method_handler(item, Position::Prologue) @@ -259,6 +268,15 @@ pub fn is_get_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_post_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_post_method_handler(item, Position::Prologue) @@ -298,6 +316,15 @@ pub fn is_post_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_put_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_put_method_handler(item, Position::Prologue) @@ -337,6 +364,15 @@ pub fn is_put_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_delete_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_delete_method_handler(item, Position::Prologue) @@ -376,6 +412,15 @@ pub fn is_delete_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_patch_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_patch_method_handler(item, Position::Prologue) @@ -415,6 +460,15 @@ pub fn is_patch_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_head_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_head_method_handler(item, Position::Prologue) @@ -454,6 +508,15 @@ pub fn is_head_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_options_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_options_method_handler(item, Position::Prologue) @@ -493,6 +556,15 @@ pub fn is_options_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_connect_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_connect_method_handler(item, Position::Prologue) @@ -532,6 +604,15 @@ pub fn is_connect_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_trace_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_trace_method_handler(item, Position::Prologue) @@ -576,6 +657,15 @@ pub fn is_trace_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_unknown_method(_attr: TokenStream, item: TokenStream) -> TokenStream { is_unknown_method_handler(item, Position::Prologue) @@ -619,6 +709,15 @@ pub fn is_unknown_method(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a comma-separated list of HTTP method names (lowercase) and should be /// applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn methods(attr: TokenStream, item: TokenStream) -> TokenStream { methods_macro(attr, item, Position::Prologue) @@ -658,6 +757,15 @@ pub fn methods(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_http0_9_version(_attr: TokenStream, item: TokenStream) -> TokenStream { is_http0_9_version_macro(item, Position::Prologue) @@ -697,6 +805,15 @@ pub fn is_http0_9_version(_attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_http1_0_version(_attr: TokenStream, item: TokenStream) -> TokenStream { is_http1_0_version_macro(item, Position::Prologue) @@ -736,6 +853,15 @@ pub fn is_http1_0_version(_attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_http1_1_version(_attr: TokenStream, item: TokenStream) -> TokenStream { is_http1_1_version_macro(item, Position::Prologue) @@ -775,6 +901,15 @@ pub fn is_http1_1_version(_attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_http2_version(_attr: TokenStream, item: TokenStream) -> TokenStream { is_http2_version_macro(item, Position::Prologue) @@ -814,6 +949,15 @@ pub fn is_http2_version(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_http3_version(_attr: TokenStream, item: TokenStream) -> TokenStream { is_http3_version_macro(item, Position::Prologue) @@ -853,6 +997,15 @@ pub fn is_http3_version(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_http1_1_or_higher_version(_attr: TokenStream, item: TokenStream) -> TokenStream { is_http1_1_or_higher_version_macro(item, Position::Prologue) @@ -892,6 +1045,15 @@ pub fn is_http1_1_or_higher_version(_attr: TokenStream, item: TokenStream) -> To /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_http_version(_attr: TokenStream, item: TokenStream) -> TokenStream { is_http_version_macro(item, Position::Prologue) @@ -931,6 +1093,15 @@ pub fn is_http_version(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_unknown_version(_attr: TokenStream, item: TokenStream) -> TokenStream { is_unknown_version_macro(item, Position::Prologue) @@ -974,6 +1145,15 @@ pub fn is_unknown_version(_attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_ws_upgrade_type(_attr: TokenStream, item: TokenStream) -> TokenStream { is_ws_upgrade_type_macro(item, Position::Prologue) @@ -1013,6 +1193,15 @@ pub fn is_ws_upgrade_type(_attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_h2c_upgrade_type(_attr: TokenStream, item: TokenStream) -> TokenStream { is_h2c_upgrade_type_macro(item, Position::Prologue) @@ -1052,6 +1241,15 @@ pub fn is_h2c_upgrade_type(_attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_tls_upgrade_type(_attr: TokenStream, item: TokenStream) -> TokenStream { is_tls_upgrade_type_macro(item, Position::Prologue) @@ -1091,6 +1289,15 @@ pub fn is_tls_upgrade_type(_attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn is_unknown_upgrade_type(_attr: TokenStream, item: TokenStream) -> TokenStream { is_unknown_upgrade_type_macro(item, Position::Prologue) @@ -1132,6 +1339,15 @@ pub fn is_unknown_upgrade_type(_attr: TokenStream, item: TokenStream) -> TokenSt /// /// The macro accepts a numeric HTTP status code or a global constant /// and should be applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn response_status_code(attr: TokenStream, item: TokenStream) -> TokenStream { response_status_code_macro(attr, item, Position::Prologue) @@ -1173,6 +1389,15 @@ pub fn response_status_code(attr: TokenStream, item: TokenStream) -> TokenStream /// /// The macro accepts a string literal or global constant for the reason phrase and should be /// applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn response_reason_phrase(attr: TokenStream, item: TokenStream) -> TokenStream { response_reason_phrase_macro(attr, item, Position::Prologue) @@ -1231,6 +1456,15 @@ pub fn response_reason_phrase(attr: TokenStream, item: TokenStream) -> TokenStre /// The macro accepts header name and header value, both can be string literals or global constants. /// Use `"key", "value"` for setting headers and `"key" => "value"` for replacing headers. /// Should be applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn response_header(attr: TokenStream, item: TokenStream) -> TokenStream { response_header_macro(attr, item, Position::Prologue) @@ -1272,6 +1506,15 @@ pub fn response_header(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a string literal or global constant for the response body and should be /// applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn response_body(attr: TokenStream, item: TokenStream) -> TokenStream { response_body_macro(attr, item, Position::Prologue) @@ -1313,6 +1556,15 @@ pub fn response_body(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` /// /// The macro should be applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn clear_response_headers(_attr: TokenStream, item: TokenStream) -> TokenStream { clear_response_headers_macro(item, Position::Prologue) @@ -1356,6 +1608,15 @@ pub fn clear_response_headers(_attr: TokenStream, item: TokenStream) -> TokenStr /// /// The macro accepts a variable or code block for the response version and should be /// applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn response_version(attr: TokenStream, item: TokenStream) -> TokenStream { response_version_macro(attr, item, Position::Prologue) @@ -1395,6 +1656,15 @@ pub fn response_version(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn closed(_attr: TokenStream, item: TokenStream) -> TokenStream { closed_macro(item, Position::Prologue) @@ -1433,6 +1703,15 @@ pub fn closed(_attr: TokenStream, item: TokenStream) -> TokenStream { /// #[filter(true)] /// async fn standalone_filter_handler(_: &mut Stream, ctx: &mut Context) -> Status { Status::Continue } /// ``` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn filter(attr: TokenStream, item: TokenStream) -> TokenStream { filter_macro(attr, item, Position::Prologue) @@ -1470,6 +1749,15 @@ pub fn filter(attr: TokenStream, item: TokenStream) -> TokenStream { /// #[reject(false)] /// async fn standalone_reject_handler(_: &mut Stream, ctx: &mut Context) -> Status { Status::Continue } /// ``` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn reject(attr: TokenStream, item: TokenStream) -> TokenStream { reject_macro(attr, item, Position::Prologue) @@ -1510,6 +1798,15 @@ pub fn reject(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a string literal specifying the expected host value and should be /// applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn host(attr: TokenStream, item: TokenStream) -> TokenStream { host_macro(attr, item, Position::Prologue) @@ -1552,6 +1849,15 @@ pub fn host(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn reject_host(attr: TokenStream, item: TokenStream) -> TokenStream { reject_host_macro(attr, item, Position::Prologue) @@ -1594,6 +1900,15 @@ pub fn reject_host(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a string literal specifying the expected referer value and should be /// applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn referer(attr: TokenStream, item: TokenStream) -> TokenStream { referer_macro(attr, item, Position::Prologue) @@ -1636,6 +1951,15 @@ pub fn referer(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a string literal specifying the referer value to filter out and should be /// applied to async functions that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn reject_referer(attr: TokenStream, item: TokenStream) -> TokenStream { reject_referer_macro(attr, item, Position::Prologue) @@ -1713,6 +2037,15 @@ pub fn reject_referer(attr: TokenStream, item: TokenStream) -> TokenStream { /// async fn method_hook(_: &mut Stream, ctx: &mut Context) -> Status { Status::Continue } /// } /// ``` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn prologue_hooks(attr: TokenStream, item: TokenStream) -> TokenStream { prologue_hooks_macro(attr, item, Position::Prologue) @@ -1790,6 +2123,15 @@ pub fn prologue_hooks(attr: TokenStream, item: TokenStream) -> TokenStream { /// async fn method_hook(_: &mut Stream, ctx: &mut Context) -> Status { Status::Continue } /// } /// ``` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn epilogue_hooks(attr: TokenStream, item: TokenStream) -> TokenStream { epilogue_hooks_macro(attr, item, Position::Epilogue) @@ -1850,6 +2192,15 @@ pub fn epilogue_hooks(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts one or more variable names separated by commas. /// Each variable will be available in the function scope as a `RequestBody` type. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_body(attr: TokenStream, item: TokenStream) -> TokenStream { request_body_macro(attr, item, Position::Prologue) @@ -1929,6 +2280,15 @@ pub fn request_body(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts one or more `variable_name: Type` pairs separated by commas. /// Each variable will be available in the function scope as a `Result`. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_body_json_result(attr: TokenStream, item: TokenStream) -> TokenStream { request_body_json_result_macro(attr, item, Position::Prologue) @@ -2012,6 +2372,15 @@ pub fn request_body_json_result(attr: TokenStream, item: TokenStream) -> TokenSt /// # Panics /// /// This macro will panic if the request body does not exist or JSON parsing fails. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_body_json(attr: TokenStream, item: TokenStream) -> TokenStream { request_body_json_macro(attr, item, Position::Prologue) @@ -2084,6 +2453,15 @@ pub fn request_body_json(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` /// /// The macro accepts multiple `key => variable_name: Type` tuples separated by commas. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_get_attribute(attr: TokenStream, item: TokenStream) -> TokenStream { try_get_attribute_macro(attr, item, Position::Prologue) @@ -2160,6 +2538,15 @@ pub fn try_get_attribute(attr: TokenStream, item: TokenStream) -> TokenStream { /// # Panics /// /// This macro will panic if the requested attribute does not exist in the request context. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn attribute(attr: TokenStream, item: TokenStream) -> TokenStream { attribute_macro(attr, item, Position::Prologue) @@ -2222,6 +2609,15 @@ pub fn attribute(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` /// /// The macro accepts multiple variable names separated by commas. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn attributes(attr: TokenStream, item: TokenStream) -> TokenStream { attributes_macro(attr, item, Position::Prologue) @@ -2285,6 +2681,15 @@ pub fn attributes(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` /// /// The macro accepts multiple variable names separated by commas. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_get_task_panic_data(attr: TokenStream, item: TokenStream) -> TokenStream { try_get_task_panic_data_macro(attr, item, Position::Prologue) @@ -2352,6 +2757,15 @@ pub fn try_get_task_panic_data(attr: TokenStream, item: TokenStream) -> TokenStr /// # Panics /// /// This macro will panic if no panic data exists in the request context. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn task_panic_data(attr: TokenStream, item: TokenStream) -> TokenStream { task_panic_data_macro(attr, item, Position::Prologue) @@ -2415,6 +2829,15 @@ pub fn task_panic_data(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` /// /// The macro accepts multiple variable names separated by commas. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_get_request_error_data(attr: TokenStream, item: TokenStream) -> TokenStream { try_get_request_error_data_macro(attr, item, Position::Prologue) @@ -2482,6 +2905,15 @@ pub fn try_get_request_error_data(attr: TokenStream, item: TokenStream) -> Token /// # Panics /// /// This macro will panic if no request error data exists in the request context. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_error_data(attr: TokenStream, item: TokenStream) -> TokenStream { request_error_data_macro(attr, item, Position::Prologue) @@ -2545,6 +2977,15 @@ pub fn request_error_data(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` /// /// The macro accepts multiple `"key" => variable_name` pairs separated by commas. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_get_route_param(attr: TokenStream, item: TokenStream) -> TokenStream { try_get_route_param_macro(attr, item, Position::Prologue) @@ -2613,6 +3054,15 @@ pub fn try_get_route_param(attr: TokenStream, item: TokenStream) -> TokenStream /// # Panics /// /// This macro will panic if the requested route parameter does not exist in the URL path. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn route_param(attr: TokenStream, item: TokenStream) -> TokenStream { route_param_macro(attr, item, Position::Prologue) @@ -2675,6 +3125,15 @@ pub fn route_param(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` /// /// The macro accepts multiple variable names separated by commas. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn route_params(attr: TokenStream, item: TokenStream) -> TokenStream { route_params_macro(attr, item, Position::Prologue) @@ -2721,6 +3180,15 @@ pub fn route_params(attr: TokenStream, item: TokenStream) -> TokenStream { /// The variable will be available as an `Option` in the function scope. /// /// Supports multiple parameters: `#[try_get_request_query("k1" => v1, "k2" => v2)]` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_get_request_query(attr: TokenStream, item: TokenStream) -> TokenStream { try_get_request_query_macro(attr, item, Position::Prologue) @@ -2771,6 +3239,15 @@ pub fn try_get_request_query(attr: TokenStream, item: TokenStream) -> TokenStrea /// # Panics /// /// This macro will panic if the requested query parameter does not exist in the URL query string. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_query(attr: TokenStream, item: TokenStream) -> TokenStream { request_query_macro(attr, item, Position::Prologue) @@ -2816,6 +3293,15 @@ pub fn request_query(attr: TokenStream, item: TokenStream) -> TokenStream { /// The variable will be available as a collection in the function scope. /// /// Supports multiple parameters: `#[request_querys(querys1, querys2)]` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_querys(attr: TokenStream, item: TokenStream) -> TokenStream { request_querys_macro(attr, item, Position::Prologue) @@ -2860,6 +3346,15 @@ pub fn request_querys(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a request header name-to-variable mapping in the format `HEADER_NAME => variable_name` /// or `"Header-Name" => variable_name`. The variable will be available as an `Option`. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_get_request_header(attr: TokenStream, item: TokenStream) -> TokenStream { try_get_request_header_macro(attr, item, Position::Prologue) @@ -2908,6 +3403,15 @@ pub fn try_get_request_header(attr: TokenStream, item: TokenStream) -> TokenStre /// # Panics /// /// This macro will panic if the requested header does not exist in the HTTP request headers. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_header(attr: TokenStream, item: TokenStream) -> TokenStream { request_header_macro(attr, item, Position::Prologue) @@ -2951,6 +3455,15 @@ pub fn request_header(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a variable name that will contain all HTTP request headers. /// The variable will be available as a RequestHeaders type in the function scope. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_headers(attr: TokenStream, item: TokenStream) -> TokenStream { request_headers_macro(attr, item, Position::Prologue) @@ -2994,6 +3507,15 @@ pub fn request_headers(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// For specific cookie extraction, the variable will be available as `Option`. /// For all cookies extraction, the variable will be available as `String`. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_get_request_cookie(attr: TokenStream, item: TokenStream) -> TokenStream { try_get_request_cookie_macro(attr, item, Position::Prologue) @@ -3041,6 +3563,15 @@ pub fn try_get_request_cookie(attr: TokenStream, item: TokenStream) -> TokenStre /// # Panics /// /// This macro will panic if the requested cookie does not exist in the HTTP request headers. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_cookie(attr: TokenStream, item: TokenStream) -> TokenStream { request_cookie_macro(attr, item, Position::Prologue) @@ -3101,6 +3632,15 @@ pub fn request_cookie(attr: TokenStream, item: TokenStream) -> TokenStream { /// async fn handle(self, _: &mut Stream, ctx: &mut Context) -> Status { Status::Continue } /// } /// ``` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_cookies(attr: TokenStream, item: TokenStream) -> TokenStream { request_cookies_macro(attr, item, Position::Prologue) @@ -3141,6 +3681,15 @@ pub fn request_cookies(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a variable name that will contain the HTTP request version. /// The variable will be available as a RequestVersion type in the function scope. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_version(attr: TokenStream, item: TokenStream) -> TokenStream { request_version_macro(attr, item, Position::Prologue) @@ -3181,6 +3730,15 @@ pub fn request_version(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a variable name that will contain the HTTP request path. /// The variable will be available as a RequestPath type in the function scope. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_path(attr: TokenStream, item: TokenStream) -> TokenStream { request_path_macro(attr, item, Position::Prologue) @@ -3238,6 +3796,15 @@ pub fn request_path(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts a `variable_name: Type` pair. /// The variable will be available as an instance of the specified type in the function scope. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn hyperlane(attr: TokenStream, item: TokenStream) -> TokenStream { hyperlane_macro(attr, item) @@ -3274,6 +3841,15 @@ pub fn hyperlane(attr: TokenStream, item: TokenStream) -> TokenStream { /// # Dependencies /// /// This macro depends on the `#[hyperlane(server: Server)]` macro to define the server instance. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { route_macro(attr, item) @@ -3314,6 +3890,15 @@ pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream { /// # Dependencies /// /// This macro depends on the `#[hyperlane(server: Server)]` macro to define the server instance. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_middleware(attr: TokenStream, item: TokenStream) -> TokenStream { request_middleware_macro(attr, item) @@ -3349,6 +3934,15 @@ pub fn request_middleware(attr: TokenStream, item: TokenStream) -> TokenStream { /// # Dependencies /// /// This macro depends on the `#[hyperlane(server: Server)]` macro to define the server instance. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn response_middleware(attr: TokenStream, item: TokenStream) -> TokenStream { response_middleware_macro(attr, item) @@ -3387,6 +3981,15 @@ pub fn response_middleware(attr: TokenStream, item: TokenStream) -> TokenStream /// # Dependencies /// /// This macro depends on the `#[hyperlane(server: Server)]` macro to define the server instance. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn task_panic(attr: TokenStream, item: TokenStream) -> TokenStream { task_panic_macro(attr, item) @@ -3425,6 +4028,15 @@ pub fn task_panic(attr: TokenStream, item: TokenStream) -> TokenStream { /// # Dependencies /// /// This macro depends on the `#[hyperlane(server: Server)]` macro to define the server instance. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn request_error(attr: TokenStream, item: TokenStream) -> TokenStream { request_error_macro(attr, item) @@ -3453,6 +4065,15 @@ pub fn request_error(attr: TokenStream, item: TokenStream) -> TokenStream { /// async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status { Status::Continue } /// } /// ``` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn prologue_macros(attr: TokenStream, item: TokenStream) -> TokenStream { prologue_macros_macro(attr, item) @@ -3481,6 +4102,15 @@ pub fn prologue_macros(attr: TokenStream, item: TokenStream) -> TokenStream { /// async fn handle(self, stream: &mut Stream, ctx: &mut Context) -> Status { Status::Continue } /// } /// ``` +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn epilogue_macros(attr: TokenStream, item: TokenStream) -> TokenStream { epilogue_macros_macro(attr, item) @@ -3542,6 +4172,15 @@ pub fn epilogue_macros(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro accepts an optional data expression. If omitted, it defaults to sending /// the response built from the context. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_send(attr: TokenStream, item: TokenStream) -> TokenStream { try_send_macro(attr, item, Position::Epilogue) @@ -3607,6 +4246,15 @@ pub fn try_send(attr: TokenStream, item: TokenStream) -> TokenStream { /// # Panics /// /// This macro will panic if the send operation fails. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn send(attr: TokenStream, item: TokenStream) -> TokenStream { send_macro(attr, item, Position::Epilogue) @@ -3646,6 +4294,15 @@ pub fn send(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// The macro takes no parameters and should be applied directly to async functions /// that accept a `&mut Context` parameter. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn try_flush(_attr: TokenStream, item: TokenStream) -> TokenStream { try_flush_macro(item, Position::Prologue) @@ -3689,6 +4346,15 @@ pub fn try_flush(_attr: TokenStream, item: TokenStream) -> TokenStream { /// # Panics /// /// This macro will panic if the flush operation fails. +/// +/// # Arguments +/// +/// - `TokenStream` - The attribute token stream. +/// - `TokenStream` - The token stream of the item to be processed. +/// +/// # Returns +/// +/// - `TokenStream` - The expanded token stream. #[proc_macro_attribute] pub fn flush(_attr: TokenStream, item: TokenStream) -> TokenStream { flush_macro(item, Position::Prologue) diff --git a/macros/src/method/fn.rs b/macros/src/method/fn.rs index c0420174..1d253dcd 100644 --- a/macros/src/method/fn.rs +++ b/macros/src/method/fn.rs @@ -47,14 +47,18 @@ pub(crate) fn methods_macro( let sig: &Signature = &input_fn.sig; match parse_context_from_signature(sig) { Ok(context) => { - let method_checks = methods.methods.iter().map(|method: &syn::Ident| { - let method_str: String = method.to_string(); - let check_fn: proc_macro2::Ident = - Ident::new(&format!("is_{method_str}"), method.span()); - quote! { - #context.get_request().get_method().#check_fn() - } - }); + let method_checks: Vec = methods + .methods + .iter() + .map(|method: &syn::Ident| { + let method_str: String = method.to_string(); + let check_fn: proc_macro2::Ident = + Ident::new(&format!("is_{method_str}"), method.span()); + quote! { + #context.get_request().get_method().#check_fn() + } + }) + .collect(); inject( position, TokenStream::from(quote! { #input_fn }), diff --git a/macros/src/referer/fn.rs b/macros/src/referer/fn.rs index 87f76ef6..040e94a3 100644 --- a/macros/src/referer/fn.rs +++ b/macros/src/referer/fn.rs @@ -19,13 +19,13 @@ pub(crate) fn referer_macro( ) -> TokenStream { let multi_referer: MultiRefererData = parse_macro_input!(attr as MultiRefererData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_referer.referer_values.iter().map(|referer_value: &syn::Expr| { + let statements: Vec = multi_referer.referer_values.iter().map(|referer_value: &syn::Expr| { quote! { if #context.get_request().try_get_header_back(::hyperlane::REFERER).map_or(true, |referer_header: ::hyperlane::RequestHeadersValueItem| referer_header != #referer_value) { return ::hyperlane::Status::Continue; } } - }); + }).collect(); quote! { #(#statements)* } @@ -51,13 +51,13 @@ pub(crate) fn reject_referer_macro( ) -> TokenStream { let multi_referer: MultiRefererData = parse_macro_input!(attr as MultiRefererData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_referer.referer_values.iter().map(|referer_value: &syn::Expr| { + let statements: Vec = multi_referer.referer_values.iter().map(|referer_value: &syn::Expr| { quote! { if #context.get_request().try_get_header_back(::hyperlane::REFERER).map_or(false, |referer_header: ::hyperlane::RequestHeadersValueItem| referer_header == #referer_value) { return ::hyperlane::Status::Continue; } } - }); + }).collect(); quote! { #(#statements)* } diff --git a/macros/src/referer/impl.rs b/macros/src/referer/impl.rs index e400b17c..918ede7a 100644 --- a/macros/src/referer/impl.rs +++ b/macros/src/referer/impl.rs @@ -13,6 +13,15 @@ use super::*; /// /// - `syn::Result` - Parsed MultiRefererData or error. impl Parse for MultiRefererData { + /// Parses the input token stream into a MultiRefererData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiRefererData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut referer_values: Vec = Vec::new(); loop { diff --git a/macros/src/request/fn.rs b/macros/src/request/fn.rs index 62b0d8e6..cd0ec60f 100644 --- a/macros/src/request/fn.rs +++ b/macros/src/request/fn.rs @@ -20,11 +20,11 @@ pub(crate) fn request_body_macro( let multi_body: MultiRequestBodyData = parse_macro_input!(attr as MultiRequestBodyData); inject(position, item, |context: &Ident, _: &Ident| { let new_context: proc_macro2::TokenStream = leak_context(false, context); - let statements = multi_body.variables.iter().map(|variable: &syn::Ident| { + let statements: Vec = multi_body.variables.iter().map(|variable: &syn::Ident| { quote! { let #variable: &::hyperlane::RequestBody = #new_context.get_request().get_body(); } - }); + }).collect(); quote! { #(#statements)* } @@ -51,11 +51,11 @@ pub(crate) fn request_body_json_result_macro( let multi_body_json: MultiRequestBodyJsonData = parse_macro_input!(attr as MultiRequestBodyJsonData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_body_json.params.iter().map(|(variable, type_name): &(syn::Ident, syn::Type)| { + let statements: Vec = multi_body_json.params.iter().map(|(variable, type_name): &(syn::Ident, syn::Type)| { quote! { let #variable: Result<#type_name, ::hyperlane::serde_json::Error> = #context.get_request().try_get_body_json::<#type_name>(); } - }); + }).collect(); quote! { #(#statements)* } @@ -82,11 +82,11 @@ pub(crate) fn request_body_json_macro( let multi_body_json: MultiRequestBodyJsonData = parse_macro_input!(attr as MultiRequestBodyJsonData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_body_json.params.iter().map(|(variable, type_name): &(syn::Ident, syn::Type)| { + let statements: Vec = multi_body_json.params.iter().map(|(variable, type_name): &(syn::Ident, syn::Type)| { quote! { let #variable: #type_name = #context.get_request().get_body_json::<#type_name>(); } - }); + }).collect(); quote! { #(#statements)* } @@ -112,13 +112,17 @@ pub(crate) fn try_get_attribute_macro( ) -> TokenStream { let multi_attr: MultiAttributeData = parse_macro_input!(attr as MultiAttributeData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_attr.params.iter().map( - |(key_name, variable, type_name): &(syn::Expr, syn::Ident, syn::Type)| { - quote! { - let #variable: Option<#type_name> = #context.try_get_attribute(&#key_name); - } - }, - ); + let statements: Vec = multi_attr + .params + .iter() + .map( + |(key_name, variable, type_name): &(syn::Expr, syn::Ident, syn::Type)| { + quote! { + let #variable: Option<#type_name> = #context.try_get_attribute(&#key_name); + } + }, + ) + .collect(); quote! { #(#statements)* } @@ -144,13 +148,17 @@ pub(crate) fn attribute_macro( ) -> TokenStream { let multi_attr: MultiAttributeData = parse_macro_input!(attr as MultiAttributeData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_attr.params.iter().map( - |(key_name, variable, type_name): &(syn::Expr, syn::Ident, syn::Type)| { - quote! { - let #variable: #type_name = #context.get_attribute(&#key_name); - } - }, - ); + let statements: Vec = multi_attr + .params + .iter() + .map( + |(key_name, variable, type_name): &(syn::Expr, syn::Ident, syn::Type)| { + quote! { + let #variable: #type_name = #context.get_attribute(&#key_name); + } + }, + ) + .collect(); quote! { #(#statements)* } @@ -177,11 +185,11 @@ pub(crate) fn attributes_macro( let multi_attrs: MultiAttributesData = parse_macro_input!(attr as MultiAttributesData); inject(position, item, |context: &Ident, _: &Ident| { let new_context: proc_macro2::TokenStream = leak_context(false, context); - let statements = multi_attrs.variables.iter().map(|variable: &syn::Ident| { + let statements: Vec = multi_attrs.variables.iter().map(|variable: &syn::Ident| { quote! { let #variable: &::hyperlane::ThreadSafeAttributeStore = #new_context.get_attributes(); } - }); + }).collect(); quote! { #(#statements)* } @@ -207,11 +215,11 @@ pub(crate) fn try_get_task_panic_data_macro( ) -> TokenStream { let multi_task_panic_data: MultiPanicData = parse_macro_input!(attr as MultiPanicData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_task_panic_data.variables.iter().map(|variable: &syn::Ident| { + let statements: Vec = multi_task_panic_data.variables.iter().map(|variable: &syn::Ident| { quote! { let #variable: Option<::hyperlane::PanicData> = #context.try_get_task_panic_data(); } - }); + }).collect(); quote! { #(#statements)* } @@ -237,14 +245,15 @@ pub(crate) fn task_panic_data_macro( ) -> TokenStream { let multi_task_panic_data: MultiPanicData = parse_macro_input!(attr as MultiPanicData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_task_panic_data + let statements: Vec = multi_task_panic_data .variables .iter() .map(|variable: &syn::Ident| { quote! { let #variable: ::hyperlane::PanicData = #context.get_task_panic_data(); } - }); + }) + .collect(); quote! { #(#statements)* } @@ -270,11 +279,11 @@ pub(crate) fn try_get_request_error_data_macro( ) -> TokenStream { let multi_error_data: MultiRequestErrorData = parse_macro_input!(attr as MultiRequestErrorData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_error_data.variables.iter().map(|variable: &syn::Ident| { + let statements: Vec = multi_error_data.variables.iter().map(|variable: &syn::Ident| { quote! { let #variable: Option<::hyperlane::RequestError> = #context.try_get_request_error_data(); } - }); + }).collect(); quote! { #(#statements)* } @@ -300,14 +309,15 @@ pub(crate) fn request_error_data_macro( ) -> TokenStream { let multi_error_data: MultiRequestErrorData = parse_macro_input!(attr as MultiRequestErrorData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_error_data + let statements: Vec = multi_error_data .variables .iter() .map(|variable: &syn::Ident| { quote! { let #variable: ::hyperlane::RequestError = #context.get_request_error_data(); } - }); + }) + .collect(); quote! { #(#statements)* } @@ -333,11 +343,15 @@ pub(crate) fn try_get_route_param_macro( ) -> TokenStream { let multi_param: MultiRouteParamData = parse_macro_input!(attr as MultiRouteParamData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_param.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { - quote! { - let #variable: Option = #context.try_get_route_param(#key_name); - } - }); + let statements: Vec = multi_param + .params + .iter() + .map(|(key_name, variable): &(syn::Expr, syn::Ident)| { + quote! { + let #variable: Option = #context.try_get_route_param(#key_name); + } + }) + .collect(); quote! { #(#statements)* } @@ -363,15 +377,15 @@ pub(crate) fn route_param_macro( ) -> TokenStream { let multi_param: MultiRouteParamData = parse_macro_input!(attr as MultiRouteParamData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = - multi_param - .params - .iter() - .map(|(key_name, variable): &(syn::Expr, syn::Ident)| { - quote! { - let #variable: std::string::String = #context.get_route_param(#key_name); - } - }); + let statements: Vec = multi_param + .params + .iter() + .map(|(key_name, variable): &(syn::Expr, syn::Ident)| { + quote! { + let #variable: String = #context.get_route_param(#key_name); + } + }) + .collect(); quote! { #(#statements)* } @@ -398,14 +412,15 @@ pub(crate) fn route_params_macro( let multi_route_params: MultiRouteParamsData = parse_macro_input!(attr as MultiRouteParamsData); inject(position, item, |context: &Ident, _: &Ident| { let new_context: proc_macro2::TokenStream = leak_context(false, context); - let statements = multi_route_params + let statements: Vec = multi_route_params .variables .iter() .map(|variable: &syn::Ident| { quote! { let #variable: &::hyperlane::RouteParams = #new_context.get_route_params(); } - }); + }) + .collect(); quote! { #(#statements)* } @@ -431,11 +446,11 @@ pub(crate) fn try_get_request_query_macro( ) -> TokenStream { let multi_query: MultiQueryData = parse_macro_input!(attr as MultiQueryData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_query.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { + let statements: Vec = multi_query.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { quote! { let #variable: Option<::hyperlane::RequestQuerysValue> = #context.get_request().try_get_query(#key_name); } - }); + }).collect(); quote! { #(#statements)* } @@ -461,11 +476,11 @@ pub(crate) fn request_query_macro( ) -> TokenStream { let multi_query: MultiQueryData = parse_macro_input!(attr as MultiQueryData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_query.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { + let statements: Vec = multi_query.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { quote! { let #variable: ::hyperlane::RequestQuerysValue = #context.get_request().get_query(#key_name); } - }); + }).collect(); quote! { #(#statements)* } @@ -492,11 +507,11 @@ pub(crate) fn request_querys_macro( let multi_querys: MultiQuerysData = parse_macro_input!(attr as MultiQuerysData); inject(position, item, |context: &Ident, _: &Ident| { let new_context: proc_macro2::TokenStream = leak_context(false, context); - let statements = multi_querys.variables.iter().map(|variable: &syn::Ident| { + let statements: Vec = multi_querys.variables.iter().map(|variable: &syn::Ident| { quote! { let #variable: &::hyperlane::RequestQuerys = #new_context.get_request().get_querys(); } - }); + }).collect(); quote! { #(#statements)* } @@ -522,11 +537,11 @@ pub(crate) fn try_get_request_header_macro( ) -> TokenStream { let multi_header: MultiHeaderData = parse_macro_input!(attr as MultiHeaderData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_header.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { + let statements: Vec = multi_header.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { quote! { let #variable: Option<::hyperlane::RequestHeadersValueItem> = #context.get_request().try_get_header_back(#key_name); } - }); + }).collect(); quote! { #(#statements)* } @@ -552,11 +567,11 @@ pub(crate) fn request_header_macro( ) -> TokenStream { let multi_header: MultiHeaderData = parse_macro_input!(attr as MultiHeaderData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_header.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { + let statements: Vec = multi_header.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { quote! { let #variable: ::hyperlane::RequestHeadersValueItem = #context.get_request().get_header_back(#key_name); } - }); + }).collect(); quote! { #(#statements)* } @@ -583,11 +598,11 @@ pub(crate) fn request_headers_macro( let multi_headers: MultiHeadersData = parse_macro_input!(attr as MultiHeadersData); inject(position, item, |context: &Ident, _: &Ident| { let new_context: proc_macro2::TokenStream = leak_context(false, context); - let statements = multi_headers.variables.iter().map(|variable: &syn::Ident| { + let statements: Vec = multi_headers.variables.iter().map(|variable: &syn::Ident| { quote! { let #variable: &::hyperlane::RequestHeaders = #new_context.get_request().get_headers(); } - }); + }).collect(); quote! { #(#statements)* } @@ -613,11 +628,11 @@ pub(crate) fn try_get_request_cookie_macro( ) -> TokenStream { let multi_cookie: MultiCookieData = parse_macro_input!(attr as MultiCookieData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_cookie.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { + let statements: Vec = multi_cookie.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { quote! { let #variable: Option<::hyperlane::CookieValue> = #context.get_request().try_get_cookie(#key_name); } - }); + }).collect(); quote! { #(#statements)* } @@ -643,11 +658,11 @@ pub(crate) fn request_cookie_macro( ) -> TokenStream { let multi_cookie: MultiCookieData = parse_macro_input!(attr as MultiCookieData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_cookie.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { + let statements: Vec = multi_cookie.params.iter().map(|(key_name, variable): &(syn::Expr, syn::Ident)| { quote! { let #variable: ::hyperlane::CookieValue = #context.get_request().get_cookie(#key_name); } - }); + }).collect(); quote! { #(#statements)* } @@ -673,11 +688,15 @@ pub(crate) fn request_cookies_macro( ) -> TokenStream { let multi_cookies: MultiCookiesData = parse_macro_input!(attr as MultiCookiesData); inject(position, item, |context: &Ident, _: &Ident| { - let statements = multi_cookies.variables.iter().map(|variable: &syn::Ident| { - quote! { - let #variable: ::hyperlane::Cookies = #context.get_request().get_cookies(); - } - }); + let statements: Vec = multi_cookies + .variables + .iter() + .map(|variable: &syn::Ident| { + quote! { + let #variable: ::hyperlane::Cookies = #context.get_request().get_cookies(); + } + }) + .collect(); quote! { #(#statements)* } @@ -705,11 +724,11 @@ pub(crate) fn request_version_macro( parse_macro_input!(attr as MultiRequestVersionData); inject(position, item, |context: &Ident, _: &Ident| { let new_context: proc_macro2::TokenStream = leak_context(false, context); - let statements = multi_version.variables.iter().map(|variable: &syn::Ident| { + let statements: Vec = multi_version.variables.iter().map(|variable: &syn::Ident| { quote! { let #variable: &::hyperlane::RequestVersion = #new_context.get_request().get_version(); } - }); + }).collect(); quote! { #(#statements)* } @@ -736,11 +755,11 @@ pub(crate) fn request_path_macro( let multi_path: MultiRequestPathData = parse_macro_input!(attr as MultiRequestPathData); inject(position, item, |context: &Ident, _: &Ident| { let new_context: proc_macro2::TokenStream = leak_context(false, context); - let statements = multi_path.variables.iter().map(|variable: &syn::Ident| { + let statements: Vec = multi_path.variables.iter().map(|variable: &syn::Ident| { quote! { let #variable: &::hyperlane::RequestPath = #new_context.get_request().get_path(); } - }); + }).collect(); quote! { #(#statements)* } diff --git a/macros/src/request/impl.rs b/macros/src/request/impl.rs index b0ec7ce2..e32bcb38 100644 --- a/macros/src/request/impl.rs +++ b/macros/src/request/impl.rs @@ -12,6 +12,15 @@ use super::*; /// /// - `syn::Result` - Parsed RequestMethods or error. impl Parse for RequestMethods { + /// Parses the input token stream into a RequestMethods structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed RequestMethods, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { Ok(RequestMethods { methods: Punctuated::parse_separated_nonempty(input)?, @@ -32,6 +41,15 @@ impl Parse for RequestMethods { /// /// - `syn::Result` - Parsed MultiRequestBodyData or error. impl Parse for MultiRequestBodyData { + /// Parses the input token stream into a MultiRequestBodyData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiRequestBodyData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -62,6 +80,15 @@ impl Parse for MultiRequestBodyData { /// /// - `syn::Result` - Parsed MultiRequestBodyJsonData or error. impl Parse for MultiRequestBodyJsonData { + /// Parses the input token stream into a MultiRequestBodyJsonData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiRequestBodyJsonData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut params: Vec<(Ident, Type)> = Vec::new(); loop { @@ -94,6 +121,15 @@ impl Parse for MultiRequestBodyJsonData { /// /// - `syn::Result` - Parsed MultiAttributeData or error. impl Parse for MultiAttributeData { + /// Parses the input token stream into a MultiAttributeData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiAttributeData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut params: Vec<(Expr, Ident, Type)> = Vec::new(); loop { @@ -128,6 +164,15 @@ impl Parse for MultiAttributeData { /// /// - `syn::Result` - Parsed MultiAttributesData or error. impl Parse for MultiAttributesData { + /// Parses the input token stream into a MultiAttributesData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiAttributesData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -158,6 +203,15 @@ impl Parse for MultiAttributesData { /// /// - `syn::Result` - Parsed MultiRouteParamData or error. impl Parse for MultiRouteParamData { + /// Parses the input token stream into a MultiRouteParamData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiRouteParamData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut params: Vec<(Expr, Ident)> = Vec::new(); loop { @@ -190,6 +244,15 @@ impl Parse for MultiRouteParamData { /// /// - `syn::Result` - Parsed MultiRouteParamsData or error. impl Parse for MultiRouteParamsData { + /// Parses the input token stream into a MultiRouteParamsData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiRouteParamsData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -220,6 +283,15 @@ impl Parse for MultiRouteParamsData { /// /// - `syn::Result` - Parsed MultiQueryData or error. impl Parse for MultiQueryData { + /// Parses the input token stream into a MultiQueryData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiQueryData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut params: Vec<(Expr, Ident)> = Vec::new(); loop { @@ -252,6 +324,15 @@ impl Parse for MultiQueryData { /// /// - `syn::Result` - Parsed MultiQuerysData or error. impl Parse for MultiQuerysData { + /// Parses the input token stream into a MultiQuerysData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiQuerysData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -282,6 +363,15 @@ impl Parse for MultiQuerysData { /// /// - `syn::Result` - Parsed MultiHeaderData or error. impl Parse for MultiHeaderData { + /// Parses the input token stream into a MultiHeaderData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiHeaderData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut params: Vec<(Expr, Ident)> = Vec::new(); loop { @@ -314,6 +404,15 @@ impl Parse for MultiHeaderData { /// /// - `syn::Result` - Parsed MultiHeadersData or error. impl Parse for MultiHeadersData { + /// Parses the input token stream into a MultiHeadersData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiHeadersData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -344,6 +443,15 @@ impl Parse for MultiHeadersData { /// /// - `syn::Result` - Parsed MultiCookieData or error. impl Parse for MultiCookieData { + /// Parses the input token stream into a MultiCookieData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiCookieData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut params: Vec<(Expr, Ident)> = Vec::new(); loop { @@ -376,6 +484,15 @@ impl Parse for MultiCookieData { /// /// - `syn::Result` - Parsed MultiCookiesData or error. impl Parse for MultiCookiesData { + /// Parses the input token stream into a MultiCookiesData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiCookiesData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -406,6 +523,15 @@ impl Parse for MultiCookiesData { /// /// - `syn::Result` - Parsed MultiRequestVersionData or error. impl Parse for MultiRequestVersionData { + /// Parses the input token stream into a MultiRequestVersionData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiRequestVersionData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -436,6 +562,15 @@ impl Parse for MultiRequestVersionData { /// /// - `syn::Result` - Parsed MultiRequestPathData or error. impl Parse for MultiRequestPathData { + /// Parses the input token stream into a MultiRequestPathData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiRequestPathData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -466,6 +601,15 @@ impl Parse for MultiRequestPathData { /// /// - `syn::Result` - Parsed MultiPanicData or error. impl Parse for MultiPanicData { + /// Parses the input token stream into a MultiPanicData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiPanicData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { @@ -496,6 +640,15 @@ impl Parse for MultiPanicData { /// /// - `syn::Result` - Parsed MultiRequestErrorData or error. impl Parse for MultiRequestErrorData { + /// Parses the input token stream into a MultiRequestErrorData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed MultiRequestErrorData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let mut variables: Vec = Vec::new(); loop { diff --git a/macros/src/response/const.rs b/macros/src/response/const.rs new file mode 100644 index 00000000..a08ad6b2 --- /dev/null +++ b/macros/src/response/const.rs @@ -0,0 +1,3 @@ +/// The error message used when a response header operation is neither add nor set. +pub(crate) const EXPECTED_ADD_OR_SET_OPERATION: &str = + "Expected either ',' for add operation or '=>' for set operation"; diff --git a/macros/src/response/impl.rs b/macros/src/response/impl.rs index 2f1c6d08..929092a2 100644 --- a/macros/src/response/impl.rs +++ b/macros/src/response/impl.rs @@ -4,6 +4,15 @@ use super::*; /// /// Parses header key, operation and value from input stream. impl Parse for ResponseHeaderData { + /// Parses the input token stream into a ResponseHeaderData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed ResponseHeaderData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let key: Expr = input.parse()?; let operation: HeaderOperation = if input.peek(Token![=>]) { @@ -13,10 +22,7 @@ impl Parse for ResponseHeaderData { input.parse::()?; HeaderOperation::Add } else { - return Err(syn::Error::new( - input.span(), - "Expected either ',' for add operation or '=>' for set operation", - )); + return Err(syn::Error::new(input.span(), EXPECTED_ADD_OR_SET_OPERATION)); }; let value: Expr = input.parse()?; Ok(ResponseHeaderData { @@ -31,6 +37,15 @@ impl Parse for ResponseHeaderData { /// /// Parses response body expression from input stream. impl Parse for ResponseBodyData { + /// Parses the input token stream into a ResponseBodyData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed ResponseBodyData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let body: Expr = input.parse()?; Ok(ResponseBodyData { body }) diff --git a/macros/src/response/mod.rs b/macros/src/response/mod.rs index 8d4e0754..e5f38a93 100644 --- a/macros/src/response/mod.rs +++ b/macros/src/response/mod.rs @@ -1,8 +1,9 @@ +mod r#const; mod r#enum; mod r#fn; mod r#impl; mod r#struct; -pub(crate) use {r#enum::*, r#fn::*, r#struct::*}; +pub(crate) use {r#const::*, r#enum::*, r#fn::*, r#struct::*}; use super::*; diff --git a/macros/src/route/impl.rs b/macros/src/route/impl.rs index 88158e94..0c505882 100644 --- a/macros/src/route/impl.rs +++ b/macros/src/route/impl.rs @@ -5,6 +5,15 @@ use super::*; /// This implementation defines how to parse a `TokenStream` into a `RouteAttr` struct, /// extracting the path expression from the input. impl Parse for RouteAttr { + /// Parses the input token stream into a RouteAttr structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `Result` - The parsed RouteAttr, or an error for invalid input. fn parse(input: ParseStream) -> Result { let first_expr: Expr = input.parse()?; Ok(RouteAttr { path: first_expr }) diff --git a/macros/src/send/impl.rs b/macros/src/send/impl.rs index bc9282c6..9c8e9ad1 100644 --- a/macros/src/send/impl.rs +++ b/macros/src/send/impl.rs @@ -4,6 +4,15 @@ use super::*; /// /// Parses data to send from input stream. impl Parse for SendData { + /// Parses the input token stream into a SendData structure. + /// + /// # Arguments + /// + /// - `ParseStream` - The token stream to parse. + /// + /// # Returns + /// + /// - `syn::Result` - The parsed SendData, or an error for invalid input. fn parse(input: ParseStream) -> syn::Result { let data: Expr = input.parse()?; Ok(SendData { data }) diff --git a/macros/src/stream/const.rs b/macros/src/stream/const.rs new file mode 100644 index 00000000..a9119b1b --- /dev/null +++ b/macros/src/stream/const.rs @@ -0,0 +1,5 @@ +/// The name of the generated HTTP request accessor method. +pub(crate) const TRY_GET_HTTP_REQUEST_METHOD_NAME: &str = "try_get_http_request"; + +/// The name of the generated WebSocket request accessor method. +pub(crate) const TRY_GET_WEBSOCKET_REQUEST_METHOD_NAME: &str = "try_get_websocket_request"; diff --git a/macros/src/stream/fn.rs b/macros/src/stream/fn.rs index cb547a19..485352be 100644 --- a/macros/src/stream/fn.rs +++ b/macros/src/stream/fn.rs @@ -14,14 +14,14 @@ use super::*; /// /// # Returns /// -/// - `TokenStream2` - The generated loop code as a token stream. +/// - `proc_macro2::TokenStream` - The generated loop code as a token stream. pub(crate) fn generate_http_stream( stream: &Ident, context: &Ident, data: &FromStreamData, stmts: &[Stmt], ) -> proc_macro2::TokenStream { - let method_ident: Ident = Ident::new("try_get_http_request", Span::call_site()); + let method_ident: Ident = Ident::new(TRY_GET_HTTP_REQUEST_METHOD_NAME, Span::call_site()); match data.variable_name.clone() { Some(variable_name) => { quote! { @@ -58,14 +58,14 @@ pub(crate) fn generate_http_stream( /// /// # Returns /// -/// - `TokenStream2` - The generated loop code as a token stream. +/// - `proc_macro2::TokenStream` - The generated loop code as a token stream. pub(crate) fn generate_websocket_stream( stream: &Ident, context: &Ident, data: &FromStreamData, stmts: &[Stmt], ) -> proc_macro2::TokenStream { - let method_ident: Ident = Ident::new("try_get_websocket_request", Span::call_site()); + let method_ident: Ident = Ident::new(TRY_GET_WEBSOCKET_REQUEST_METHOD_NAME, Span::call_site()); match data.variable_name.clone() { Some(variable_name) => { quote! { @@ -139,8 +139,8 @@ pub(crate) fn try_get_http_request_macro(attr: TokenStream, item: TokenStream) - /// /// # Arguments /// -/// - `attr` - The attribute containing the variable name. -/// - `item` - The input token stream to process. +/// - `TokenStream` - The attribute containing the variable name. +/// - `TokenStream` - The input token stream to process. /// /// # Returns /// diff --git a/macros/src/stream/mod.rs b/macros/src/stream/mod.rs index 910cac30..09524dbe 100644 --- a/macros/src/stream/mod.rs +++ b/macros/src/stream/mod.rs @@ -1,5 +1,6 @@ +mod r#const; mod r#fn; -pub(crate) use r#fn::*; +pub(crate) use {r#const::*, r#fn::*}; use super::*; diff --git a/plugin/websocket/src/impl.rs b/plugin/websocket/src/impl.rs index 4cb7fbe0..a680b78d 100644 --- a/plugin/websocket/src/impl.rs +++ b/plugin/websocket/src/impl.rs @@ -144,7 +144,11 @@ impl BroadcastTypeTrait for &u16 {} /// Allows references to `u32` to be used as broadcast identifiers. impl BroadcastTypeTrait for &u32 {} -/// Allows references to `u64` to be used as +/// Implements `BroadcastTypeTrait` for `&u64`. +/// +/// This allows references to `u64` to be used as a broadcast identifier. +impl BroadcastTypeTrait for &u64 {} + /// Implements `BroadcastTypeTrait` for `&u128`. /// /// This allows references to `u128` to be used as a broadcast identifier. @@ -261,6 +265,7 @@ impl Default for BroadcastType where B: BroadcastTypeTrait, { + /// Returns the default `BroadcastType`, which is `BroadcastType::Unknown`. #[inline(always)] fn default() -> Self { BroadcastType::Unknown @@ -315,7 +320,8 @@ where /// /// # Arguments /// - /// - `&mut Context` - The context object to associate with the WebSocket. + /// - `&'a mut Stream` - The stream object serving this WebSocket. + /// - `&'a mut Context` - The context object to associate with the WebSocket. /// /// # Returns /// @@ -358,7 +364,7 @@ where /// /// # Arguments /// - /// - `&mut Context` - The context object to associate with the WebSocket. + /// - `&'a mut Context` - The context object to associate with the WebSocket. /// /// # Returns /// @@ -384,6 +390,11 @@ where self } + /// Returns a mutable reference to the stream served by this configuration. + /// + /// # Returns + /// + /// - `&mut Stream` - A mutable reference to the stream. #[inline(always)] pub fn get_stream(&mut self) -> &mut Stream { self.stream @@ -605,6 +616,19 @@ impl WebSocket { Self::default() } + /// Returns a shared reference to the internal broadcast map. + /// + /// Hand-written accessor: the `WebSocket` struct does not derive the lombok + /// `Data` macro, and §17.3 forbids reading `self.broadcast_map` directly. + /// + /// # Returns + /// + /// - `&BroadcastMap>` - A shared reference to the internal broadcast map. + #[inline(always)] + pub fn get_broadcast_map(&self) -> &BroadcastMap> { + &self.broadcast_map + } + /// Subscribes to a broadcast type or inserts a new one if it doesn't exist. /// /// # Type Parameters @@ -629,7 +653,7 @@ impl WebSocket { B: BroadcastTypeTrait, { let key: String = BroadcastType::get_key(broadcast_type); - self.broadcast_map.subscribe_or_insert(&key, capacity) + self.get_broadcast_map().subscribe_or_insert(&key, capacity) } /// Subscribes to a point-to-point broadcast. @@ -640,8 +664,8 @@ impl WebSocket { /// /// # Arguments /// - /// - `&BroadcastTypeTrait` - The first identifier for the point-to-point communication. - /// - `&BroadcastTypeTrait` - The second identifier for the point-to-point communication. + /// - `&B` - The first identifier for the point-to-point communication. + /// - `&B` - The second identifier for the point-to-point communication. /// - `Capacity` - The capacity for the broadcast sender. /// /// # Returns @@ -671,7 +695,7 @@ impl WebSocket { /// /// # Arguments /// - /// - `&BroadcastTypeTrait` - The identifier for the group. + /// - `&B` - The identifier for the group. /// - `Capacity` - The capacity for the broadcast sender. /// /// # Returns @@ -704,7 +728,7 @@ impl WebSocket { B: BroadcastTypeTrait, { let key: String = BroadcastType::get_key(broadcast_type); - self.broadcast_map.receiver_count(&key).unwrap_or(0) + self.get_broadcast_map().receiver_count(&key).unwrap_or(0) } /// Calculates the receiver count before a connection is established. @@ -744,7 +768,7 @@ impl WebSocket { /// /// # Arguments /// - /// - `BroadcastType` - The broadcast type for which to get the receiver count. + /// - `BroadcastType` - The broadcast type for which to get the receiver count. /// /// # Returns /// @@ -767,8 +791,8 @@ impl WebSocket { /// /// # Arguments /// - /// - `BroadcastType` - The broadcast type to which to send the data. - /// - `Into>` - The data to send. + /// - `BroadcastType` - The broadcast type to which to send the data. + /// - `T` - The data to send. /// /// # Returns /// @@ -784,7 +808,7 @@ impl WebSocket { B: BroadcastTypeTrait, { let key: String = BroadcastType::get_key(broadcast_type); - self.broadcast_map.try_send(&key, data.into()) + self.get_broadcast_map().try_send(&key, data.into()) } /// Sends data to all active receivers for a given broadcast type. @@ -798,8 +822,8 @@ impl WebSocket { /// /// # Arguments /// - /// - `BroadcastType` - The broadcast type to which to send the data. - /// - `Into>` - The data to send. + /// - `BroadcastType` - The broadcast type to which to send the data. + /// - `T` - The data to send. /// /// # Returns /// @@ -828,7 +852,7 @@ impl WebSocket { /// /// # Arguments /// - /// - `WebSocketConfig` - The WebSocket configuration containing the configuration for this WebSocket instance. + /// - `WebSocketConfig<'_, B>` - The WebSocket configuration containing the configuration for this WebSocket instance. /// /// # Panics /// @@ -870,7 +894,7 @@ impl WebSocket { closed_hook(stream, ctx).await; } let body: ResponseBody = ctx.get_response().get_body().clone(); - let is_err: bool = self.broadcast_map.try_send(&key, body).is_err(); + let is_err: bool = self.get_broadcast_map().try_send(&key, body).is_err(); if is_err || sended_hook(stream, ctx).await.is_reject() || is_reject { break; } diff --git a/plugin/websocket/src/lib.rs b/plugin/websocket/src/lib.rs index b0f26ba7..eb0ae48a 100644 --- a/plugin/websocket/src/lib.rs +++ b/plugin/websocket/src/lib.rs @@ -1,4 +1,4 @@ -//! A WebSocket plugin for the Hyperlane framework. +//! hyperlane-plugin-websocket //! //! A WebSocket plugin for the Hyperlane framework, //! providing robust WebSocket communication capabilities and integrating @@ -10,9 +10,9 @@ mod r#impl; mod r#struct; mod r#trait; -pub use {r#enum::*, r#struct::*}; +pub use {r#enum::*, r#struct::*, r#trait::*}; -use {r#const::*, r#trait::*}; +use r#const::*; use std::{ convert::Infallible, diff --git a/plugin/websocket/tests/broadcast_type/fn.rs b/plugin/websocket/tests/broadcast_type/fn.rs new file mode 100644 index 00000000..84bb6ad7 --- /dev/null +++ b/plugin/websocket/tests/broadcast_type/fn.rs @@ -0,0 +1,281 @@ +use super::*; + +#[test] +fn point_to_point_key_is_independent_of_identifier_order() { + let ascending: String = BroadcastType::::get_key(BroadcastType::PointToPoint( + String::from("alice"), + String::from("bob"), + )); + let descending: String = BroadcastType::::get_key(BroadcastType::PointToPoint( + String::from("bob"), + String::from("alice"), + )); + assert_eq!(ascending, descending); + assert_eq!(ascending, String::from("ptp--alice-bob")); +} + +#[test] +fn point_to_point_key_repeats_a_self_paired_identifier() { + let key: String = BroadcastType::::get_key(BroadcastType::PointToPoint( + String::from("carol"), + String::from("carol"), + )); + assert_eq!(key, String::from("ptp--carol-carol")); +} + +#[test] +fn point_to_point_key_orders_numeric_identifiers_by_value() { + let key: String = BroadcastType::::get_key(BroadcastType::PointToPoint(10, 2)); + assert_eq!(key, String::from("ptp--2-10")); + let swapped: String = BroadcastType::::get_key(BroadcastType::PointToPoint(2, 10)); + assert_eq!(key, swapped); +} + +#[test] +fn point_to_point_key_orders_floating_point_identifiers_by_value() { + let key: String = BroadcastType::::get_key(BroadcastType::PointToPoint(1.5, 0.5)); + assert_eq!(key, String::from("ptp--0.5-1.5")); + let swapped: String = BroadcastType::::get_key(BroadcastType::PointToPoint(0.5, 1.5)); + assert_eq!(key, swapped); +} + +#[test] +fn point_to_group_key_is_the_group_name_behind_the_point_to_group_prefix() { + let key: String = + BroadcastType::::get_key(BroadcastType::PointToGroup(String::from("room-7"))); + assert_eq!(key, String::from("ptg--room-7")); +} + +#[test] +fn unknown_broadcast_type_key_is_the_empty_string() { + let key: String = BroadcastType::::get_key(BroadcastType::Unknown); + assert_eq!(key, String::new()); + assert!(key.is_empty()); +} + +#[test] +fn get_key_renders_scalar_payloads_through_their_display_form() { + let character: String = BroadcastType::::get_key(BroadcastType::PointToGroup('x')); + assert_eq!(character, String::from("ptg--x")); + let boolean: String = BroadcastType::::get_key(BroadcastType::PointToPoint(true, false)); + assert_eq!(boolean, String::from("ptp--false-true")); + let slice: String = BroadcastType::<&str>::get_key(BroadcastType::PointToGroup("room")); + assert_eq!(slice, String::from("ptg--room")); + let signed: String = BroadcastType::::get_key(BroadcastType::PointToPoint(-3, 4)); + assert_eq!(signed, String::from("ptp---3-4")); + let unsigned: String = BroadcastType::::get_key(BroadcastType::PointToGroup( + 340282366920938463463374607431768211455, + )); + assert_eq!( + unsigned, + String::from("ptg--340282366920938463463374607431768211455") + ); +} + +#[test] +fn get_key_renders_network_payloads_through_their_display_form() { + let v4: String = + BroadcastType::::get_key(BroadcastType::PointToGroup(Ipv4Addr::new(10, 0, 0, 1))); + assert_eq!(v4, String::from("ptg--10.0.0.1")); + let v6: String = + BroadcastType::::get_key(BroadcastType::PointToGroup(Ipv6Addr::LOCALHOST)); + assert_eq!(v6, String::from("ptg--::1")); + let ip: String = BroadcastType::::get_key(BroadcastType::PointToGroup(IpAddr::V4( + Ipv4Addr::new(127, 0, 0, 1), + ))); + assert_eq!(ip, String::from("ptg--127.0.0.1")); + let socket: String = BroadcastType::::get_key(BroadcastType::PointToGroup( + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080), + )); + assert_eq!(socket, String::from("ptg--127.0.0.1:8080")); +} + +#[test] +fn get_key_renders_non_zero_payloads_through_their_display_form() { + let nine: NonZeroU8 = NonZeroU8::new(9).unwrap_or(NonZeroU8::MIN); + let unsigned: String = BroadcastType::::get_key(BroadcastType::PointToGroup(nine)); + assert_eq!(unsigned, String::from("ptg--9")); + let minus_two: NonZeroI32 = NonZeroI32::new(-2).unwrap_or(NonZeroI32::MIN); + let signed: String = + BroadcastType::::get_key(BroadcastType::PointToGroup(minus_two)); + assert_eq!(signed, String::from("ptg---2")); + let size: NonZeroUsize = NonZeroUsize::new(1024).unwrap_or(NonZeroUsize::MIN); + let on_host: String = BroadcastType::::get_key(BroadcastType::PointToGroup(size)); + assert_eq!(on_host, String::from("ptg--1024")); +} + +#[test] +fn get_key_never_merges_a_point_to_point_key_with_a_point_to_group_key() { + let group: String = + BroadcastType::::get_key(BroadcastType::PointToGroup(String::from("a-b"))); + let pair: String = BroadcastType::::get_key(BroadcastType::PointToPoint( + String::from("a"), + String::from("b"), + )); + assert_ne!(group, pair); + let unknown: String = BroadcastType::::get_key(BroadcastType::Unknown); + assert_ne!(group, unknown); + assert_ne!(pair, unknown); +} + +#[test] +fn default_broadcast_type_is_unknown() { + let owned: BroadcastType = BroadcastType::default(); + assert_eq!(owned, BroadcastType::Unknown); + let borrowed: BroadcastType<&str> = BroadcastType::default(); + assert_eq!(borrowed, BroadcastType::Unknown); + let numeric: BroadcastType = BroadcastType::default(); + assert!(matches!(numeric, BroadcastType::Unknown)); +} + +#[test] +fn broadcast_type_is_copy_when_its_payload_is_copy() { + let original: BroadcastType = BroadcastType::PointToPoint(3, 4); + let moved: BroadcastType = original; + let reused: BroadcastType = original; + assert_eq!(moved, reused); + assert_eq!(original, BroadcastType::PointToPoint(3, 4)); +} + +#[test] +fn broadcast_type_with_an_owned_payload_survives_being_cloned() { + let original: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let clone: BroadcastType = original.clone(); + assert_eq!(original, clone); + assert!(matches!(original, BroadcastType::PointToGroup(_))); +} + +#[test] +fn distinct_broadcast_types_collapse_in_a_hash_set_by_value() { + let mut set: HashSet> = HashSet::new(); + set.insert(BroadcastType::PointToGroup(String::from("room"))); + set.insert(BroadcastType::PointToGroup(String::from("room"))); + set.insert(BroadcastType::PointToPoint( + String::from("a"), + String::from("b"), + )); + set.insert(BroadcastType::PointToPoint( + String::from("b"), + String::from("a"), + )); + set.insert(BroadcastType::Unknown); + set.insert(BroadcastType::Unknown); + assert_eq!(set.len(), 4); + assert!(set.contains(&BroadcastType::PointToPoint( + String::from("a"), + String::from("b") + ))); + assert!(set.contains(&BroadcastType::PointToPoint( + String::from("b"), + String::from("a") + ))); +} + +#[test] +fn point_to_point_equality_ignores_the_sorted_key_but_not_the_tuple_order() { + let forward: BroadcastType = + BroadcastType::PointToPoint(String::from("a"), String::from("b")); + let reverse: BroadcastType = + BroadcastType::PointToPoint(String::from("b"), String::from("a")); + assert_ne!(forward, reverse); + assert_eq!( + BroadcastType::get_key(forward.clone()), + BroadcastType::get_key(reverse) + ); + assert_eq!(forward, forward.clone()); + assert_ne!(forward, BroadcastType::Unknown); +} + +#[test] +fn debug_names_the_variant_and_renders_its_payload() { + let group: String = format!( + "{:?}", + BroadcastType::::PointToGroup(String::from("room")) + ); + assert_eq!(group, String::from("PointToGroup(\"room\")")); + let pair: String = format!("{:?}", BroadcastType::<&str>::PointToPoint("a", "b")); + assert_eq!(pair, String::from("PointToPoint(\"a\", \"b\")")); + let unknown: String = format!("{:?}", BroadcastType::::Unknown); + assert_eq!(unknown, String::from("Unknown")); +} + +#[test] +fn broadcast_type_trait_covers_owned_scalar_identifiers() { + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType<&str> = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; +} + +#[test] +fn broadcast_type_trait_covers_network_non_zero_and_infallible_identifiers() { + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; + let _: BroadcastType = BroadcastType::Unknown; +} + +#[test] +fn broadcast_type_trait_covers_reference_identifiers() { + let _: BroadcastType<&String> = BroadcastType::Unknown; + let _: BroadcastType<&&str> = BroadcastType::Unknown; + let _: BroadcastType<&char> = BroadcastType::Unknown; + let _: BroadcastType<&bool> = BroadcastType::Unknown; + let _: BroadcastType<&i8> = BroadcastType::Unknown; + let _: BroadcastType<&i16> = BroadcastType::Unknown; + let _: BroadcastType<&i32> = BroadcastType::Unknown; + let _: BroadcastType<&i64> = BroadcastType::Unknown; + let _: BroadcastType<&i128> = BroadcastType::Unknown; + let _: BroadcastType<&isize> = BroadcastType::Unknown; + let _: BroadcastType<&u8> = BroadcastType::Unknown; + let _: BroadcastType<&u16> = BroadcastType::Unknown; + let _: BroadcastType<&u32> = BroadcastType::Unknown; + let _: BroadcastType<&u128> = BroadcastType::Unknown; + let _: BroadcastType<&usize> = BroadcastType::Unknown; + let _: BroadcastType<&f32> = BroadcastType::Unknown; + let _: BroadcastType<&f64> = BroadcastType::Unknown; + let _: BroadcastType<&IpAddr> = BroadcastType::Unknown; + let _: BroadcastType<&Ipv4Addr> = BroadcastType::Unknown; + let _: BroadcastType<&Ipv6Addr> = BroadcastType::Unknown; + let _: BroadcastType<&SocketAddr> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroU8> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroU16> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroU32> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroU64> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroU128> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroUsize> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroI8> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroI16> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroI32> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroI64> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroI128> = BroadcastType::Unknown; + let _: BroadcastType<&NonZeroIsize> = BroadcastType::Unknown; + let _: BroadcastType<&Infallible> = BroadcastType::Unknown; +} diff --git a/plugin/websocket/tests/broadcast_type/mod.rs b/plugin/websocket/tests/broadcast_type/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/plugin/websocket/tests/broadcast_type/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/plugin/websocket/tests/mod.rs b/plugin/websocket/tests/mod.rs index 5ed1bea1..bebc197f 100644 --- a/plugin/websocket/tests/mod.rs +++ b/plugin/websocket/tests/mod.rs @@ -1,11 +1,23 @@ +mod broadcast_type; mod websocket; +mod websocket_map; use hyperlane_plugin_websocket::*; -use std::sync::OnceLock; +use std::{ + collections::HashSet, + convert::Infallible, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, + num::{ + NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, + }, + sync::OnceLock, + time::Duration, +}; use { hyperlane::*, - tokio::{spawn, time::sleep}, + tokio::{spawn, sync::broadcast::error::RecvError, time::sleep}, tokio_broadcast::*, }; diff --git a/plugin/websocket/tests/websocket/fn.rs b/plugin/websocket/tests/websocket/fn.rs index 6f8edafc..41e1333f 100644 --- a/plugin/websocket/tests/websocket/fn.rs +++ b/plugin/websocket/tests/websocket/fn.rs @@ -9,12 +9,12 @@ async fn main() { server.request_error::(); server.request_middleware::(); server.request_middleware::(); - server.route::("/{group_name}"); - server.route::("/{my_name}/{your_name}"); + server.route::("/{group_name}"); + server.route::("/{my_name}/{your_name}"); let server_control_hook_1: ServerControlHook = server.run().await.unwrap_or_default(); let server_control_hook_2: ServerControlHook = server_control_hook_1.clone(); spawn(async move { - sleep(std::time::Duration::from_secs(60)).await; + sleep(Duration::from_secs(60)).await; server_control_hook_2.shutdown().await; }); server_control_hook_1.wait().await; diff --git a/plugin/websocket/tests/websocket_map/fn.rs b/plugin/websocket/tests/websocket_map/fn.rs new file mode 100644 index 00000000..eb61640f --- /dev/null +++ b/plugin/websocket/tests/websocket_map/fn.rs @@ -0,0 +1,291 @@ +use super::*; + +#[test] +fn a_new_web_socket_exposes_an_empty_broadcast_map() { + let created: WebSocket = WebSocket::new(); + let defaulted: WebSocket = WebSocket::default(); + let created_missing: Option = + created.get_broadcast_map().receiver_count("absent"); + let defaulted_missing: Option = + defaulted.get_broadcast_map().receiver_count("absent"); + assert_eq!(created_missing, None); + assert_eq!(defaulted_missing, None); +} + +#[test] +fn receiver_count_is_zero_before_anything_subscribes() { + let web_socket: WebSocket = WebSocket::new(); + let group: ReceiverCount = + web_socket.receiver_count(BroadcastType::::PointToGroup(String::from("room"))); + let pair: ReceiverCount = web_socket.receiver_count(BroadcastType::::PointToPoint( + String::from("a"), + String::from("b"), + )); + let unknown: ReceiverCount = web_socket.receiver_count(BroadcastType::::Unknown); + assert_eq!(group, 0); + assert_eq!(pair, 0); + assert_eq!(unknown, 0); +} + +#[test] +fn receiver_count_tracks_every_subscriber_of_the_generated_key() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let _first: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let _second: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let count: ReceiverCount = web_socket.receiver_count(broadcast_type); + assert_eq!(count, 2); +} + +#[test] +fn both_point_to_point_orders_reach_the_same_channel() { + let web_socket: WebSocket = WebSocket::new(); + let forward: BroadcastType = + BroadcastType::PointToPoint(String::from("alice"), String::from("bob")); + let reverse: BroadcastType = + BroadcastType::PointToPoint(String::from("bob"), String::from("alice")); + let key: String = BroadcastType::get_key(forward.clone()); + let _receiver: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let forward_count: ReceiverCount = web_socket.receiver_count(forward); + let reverse_count: ReceiverCount = web_socket.receiver_count(reverse); + assert_eq!(key, String::from("ptp--alice-bob")); + assert_eq!(forward_count, 1); + assert_eq!(reverse_count, 1); +} + +#[test] +fn receiver_count_before_connected_reports_one_more_than_the_live_count() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let before_any: ReceiverCount = + web_socket.receiver_count_before_connected(broadcast_type.clone()); + let _first: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let after_one: ReceiverCount = + web_socket.receiver_count_before_connected(broadcast_type.clone()); + let _second: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let _third: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let after_three: ReceiverCount = + web_socket.receiver_count_before_connected(broadcast_type.clone()); + let untouched: ReceiverCount = + web_socket + .receiver_count_before_connected(BroadcastType::::PointToGroup(String::new())); + assert_eq!(before_any, 1); + assert_eq!(after_one, 2); + assert_eq!(after_three, 4); + assert_eq!(untouched, 1); +} + +#[test] +fn receiver_count_after_closed_never_drops_below_zero() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let with_no_subscriber: ReceiverCount = + web_socket.receiver_count_after_closed(broadcast_type.clone()); + let _first: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let with_one_subscriber: ReceiverCount = + web_socket.receiver_count_after_closed(broadcast_type.clone()); + let _second: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let with_two_subscribers: ReceiverCount = + web_socket.receiver_count_after_closed(broadcast_type); + let untouched: ReceiverCount = web_socket + .receiver_count_after_closed(BroadcastType::::PointToGroup(String::new())); + assert_eq!(with_no_subscriber, 0); + assert_eq!(with_one_subscriber, 0); + assert_eq!(with_two_subscribers, 1); + assert_eq!(untouched, 0); +} + +#[test] +fn try_send_reports_no_channel_for_an_unused_broadcast_type() { + let web_socket: WebSocket = WebSocket::new(); + let result: Result, BroadcastMapSendError>> = web_socket + .try_send( + BroadcastType::::PointToGroup(String::from("room")), + "payload", + ); + let count: Option = result.unwrap(); + assert_eq!(count, None); +} + +#[tokio::test] +async fn try_send_delivers_the_payload_to_every_subscriber() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let mut first: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let mut second: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let result: Result, BroadcastMapSendError>> = + web_socket.try_send(broadcast_type, String::from("hello")); + let count: Option = result.unwrap(); + assert_eq!(count, Some(2)); + let first_message: Result, RecvError> = first.recv().await; + let second_message: Result, RecvError> = second.recv().await; + assert_eq!(first_message.unwrap(), String::from("hello").into_bytes()); + assert_eq!(second_message.unwrap(), String::from("hello").into_bytes()); +} + +#[tokio::test] +async fn try_send_accepts_every_byte_vector_payload_shape() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let mut receiver: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let from_str: Result, BroadcastMapSendError>> = + web_socket.try_send(broadcast_type.clone(), "one"); + let from_string: Result, BroadcastMapSendError>> = + web_socket.try_send(broadcast_type.clone(), String::from("two")); + let from_bytes: Result, BroadcastMapSendError>> = + web_socket.try_send(broadcast_type, String::from("three").into_bytes()); + assert_eq!(from_str.unwrap(), Some(1)); + assert_eq!(from_string.unwrap(), Some(1)); + assert_eq!(from_bytes.unwrap(), Some(1)); + let first: Result, RecvError> = receiver.recv().await; + let second: Result, RecvError> = receiver.recv().await; + let third: Result, RecvError> = receiver.recv().await; + assert_eq!(first.unwrap(), String::from("one").into_bytes()); + assert_eq!(second.unwrap(), String::from("two").into_bytes()); + assert_eq!(third.unwrap(), String::from("three").into_bytes()); +} + +#[test] +fn try_send_fails_and_hands_the_payload_back_when_no_subscriber_is_left() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let receiver: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + drop(receiver); + let result: Result, BroadcastMapSendError>> = + web_socket.try_send(broadcast_type, String::from("hello")); + let error: BroadcastMapSendError> = result.unwrap_err(); + let message: String = error.to_string(); + assert_eq!(message, String::from("channel closed")); + let recovered: Vec = error.0; + assert_eq!(recovered, String::from("hello").into_bytes()); +} + +#[test] +fn send_returns_the_number_of_receivers() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let _receiver: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let count: Option = web_socket.send(broadcast_type, "hello"); + assert_eq!(count, Some(1)); +} + +#[test] +fn send_returns_none_for_a_channel_that_was_never_created() { + let web_socket: WebSocket = WebSocket::new(); + let count: Option = web_socket.send( + BroadcastType::::PointToGroup(String::from("room")), + "hello", + ); + assert_eq!(count, None); +} + +#[test] +#[should_panic(expected = "SendError")] +fn send_panics_when_the_channel_has_no_subscriber() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let receiver: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + drop(receiver); + let count: Option = web_socket.send(broadcast_type, "hello"); + assert_eq!(count, None); +} + +#[test] +fn unsubscribing_a_key_removes_the_whole_channel() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let _receiver: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let removed: Option>> = web_socket.get_broadcast_map().unsubscribe(&key); + assert!(removed.is_some()); + let count: ReceiverCount = web_socket.receiver_count(broadcast_type.clone()); + assert_eq!(count, 0); + let result: Result, BroadcastMapSendError>> = + web_socket.try_send(broadcast_type, "hello"); + let sent: Option = result.unwrap(); + assert_eq!(sent, None); + let removed_again: Option>> = + web_socket.get_broadcast_map().unsubscribe(&key); + assert!(removed_again.is_none()); +} + +#[test] +fn a_cloned_web_socket_keeps_counting_the_subscribers_of_the_original() { + let web_socket: WebSocket = WebSocket::new(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let _receiver: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let clone: WebSocket = web_socket.clone(); + let count: ReceiverCount = clone.receiver_count(broadcast_type); + assert_eq!(count, 1); +} + +#[test] +fn a_cloned_web_socket_does_not_share_later_map_insertions() { + let web_socket: WebSocket = WebSocket::new(); + let clone: WebSocket = web_socket.clone(); + let broadcast_type: BroadcastType = BroadcastType::PointToGroup(String::from("room")); + let key: String = BroadcastType::get_key(broadcast_type.clone()); + let _receiver: BroadcastMapReceiver> = clone + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let clone_count: ReceiverCount = clone.receiver_count(broadcast_type.clone()); + let original_count: ReceiverCount = web_socket.receiver_count(broadcast_type); + assert_eq!(clone_count, 1); + assert_eq!(original_count, 0); +} + +#[test] +fn debug_renders_the_inner_broadcast_map_without_panicking() { + let web_socket: WebSocket = WebSocket::new(); + let empty: String = format!("{:?}", web_socket); + assert!(!empty.is_empty()); + let key: String = + BroadcastType::::get_key(BroadcastType::PointToGroup(String::from("room"))); + let _receiver: BroadcastMapReceiver> = web_socket + .get_broadcast_map() + .subscribe_or_insert(&key, DEFAULT_BROADCAST_SENDER_CAPACITY); + let populated: String = format!("{:?}", web_socket); + assert!(populated.contains("ptg--room")); + assert_ne!(empty, populated); +} diff --git a/plugin/websocket/tests/websocket_map/mod.rs b/plugin/websocket/tests/websocket_map/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/plugin/websocket/tests/websocket_map/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/request/src/common/impl.rs b/request/src/common/impl.rs index bf20c739..ed52299e 100644 --- a/request/src/common/impl.rs +++ b/request/src/common/impl.rs @@ -42,7 +42,7 @@ impl Body { /// - `Option<&str>` - The body decoded as UTF-8, or `None` when the bytes /// are not valid UTF-8. pub fn as_str(&self) -> Option<&str> { - std::str::from_utf8(self.get_bytes()).ok() + from_utf8(self.get_bytes()).ok() } } diff --git a/request/src/lib.rs b/request/src/lib.rs index 0f5ef3de..6d2bb853 100644 --- a/request/src/lib.rs +++ b/request/src/lib.rs @@ -17,9 +17,11 @@ pub use http_type::{ }; pub use std::{ + cmp, collections::{HashMap, HashSet, VecDeque}, fmt::{self, Debug, Display, Formatter}, - io::{Read, Write}, + io::{self, Read, Write}, + mem, net::{Ipv4Addr, Ipv6Addr, TcpStream}, pin::Pin, str::from_utf8, diff --git a/request/src/request/http_request/const.rs b/request/src/request/http_request/const.rs index a4e442c7..67b108d0 100644 --- a/request/src/request/http_request/const.rs +++ b/request/src/request/http_request/const.rs @@ -12,3 +12,33 @@ pub(crate) const TRANSFER_ENCODING_PATTERN: &[u8] = b"transfer-encoding:"; /// /// Used for case-sensitive matching of the chunked transfer encoding value. pub(crate) const CHUNKED_PATTERN: &[u8] = b"chunked"; + +/// Error message used when the request method is neither GET nor POST. +pub(crate) const METHOD_NOT_ALLOWED: &str = "Method Not Allowed"; + +/// Error message used when a redirect was signalled but no target was found. +pub(crate) const MISSING_REDIRECT_URL: &str = "Missing Redirect URL"; + +/// Error message used when redirect handling is disabled by configuration. +pub(crate) const REDIRECT_NOT_ENABLED: &str = "Redirect Not Enabled"; + +/// Error message used when a redirect target was already visited. +pub(crate) const REDIRECT_URL_DEAD_LOOP: &str = "Redirect URL Dead Loop"; + +/// Error message used when the redirect budget is exhausted. +pub(crate) const MAX_REDIRECT_TIMES_EXCEEDED: &str = "Max Redirect Times Exceeded"; + +/// Error message used when a proxy handshake fails. +pub(crate) const INTERNAL_SERVER_ERROR: &str = "Internal Server Error"; + +/// Prefix of a successful HTTP/1.1 proxy response status line. +pub(crate) const HTTP_1_1_OK_PREFIX: &str = "HTTP/1.1 200"; + +/// Prefix of a successful HTTP/1.0 proxy response status line. +pub(crate) const HTTP_1_0_OK_PREFIX: &str = "HTTP/1.0 200"; + +/// Separator marking the end of an HTTP header block. +pub(crate) const HEADER_TERMINATOR: &str = "\r\n\r\n"; + +/// HTTP request-line method token for POST. +pub(crate) const HTTP_METHOD_POST: &str = "POST"; diff --git a/request/src/request/http_request/impl.rs b/request/src/request/http_request/impl.rs index 70f633c7..adfdc7cd 100644 --- a/request/src/request/http_request/impl.rs +++ b/request/src/request/http_request/impl.rs @@ -38,9 +38,9 @@ impl HttpRequest { /// /// - `String` - The request path, with the query string appended when present. pub(crate) fn full_path(&self) -> String { - let url_obj = self.parse_url().unwrap_or_default(); - let query = url_obj.query.unwrap_or_default(); - let path = url_obj.path.unwrap_or_default(); + let url_obj: HttpUrlComponents = self.parse_url().unwrap_or_default(); + let query: String = url_obj.query.unwrap_or_default(); + let path: String = url_obj.path.unwrap_or_default(); if query.is_empty() { path } else { @@ -116,7 +116,7 @@ impl HttpRequest { /// /// - `bool` - `true` when the map contains the key, `false` otherwise. fn header_has_key(header: &HashMap, target_key: &str) -> bool { - let target = target_key.to_ascii_lowercase(); + let target: String = target_key.to_ascii_lowercase(); header.keys().any(|k: &String| k == &target) } @@ -127,7 +127,7 @@ impl HttpRequest { /// /// - `Vec` - The encoded request body, empty when the content type is unrecognised. pub(crate) fn body_bytes(&self) -> Vec { - let ct = self + let ct: Option = self .headers .iter() .find(|(k, _): &(&String, &String)| k.eq_ignore_ascii_case(CONTENT_TYPE)) @@ -149,17 +149,17 @@ impl HttpRequest { /// /// - `RequestResult` - The parsed HTTP response, or a `RequestError` on failure. pub(crate) fn send_sync(&mut self) -> RequestResult { - let url_obj = self.parse_url()?; + let url_obj: HttpUrlComponents = self.parse_url()?; let host: String = url_obj.host.clone().unwrap_or_default(); let port: u16 = self.resolve_port(url_obj.port.unwrap_or_default()); let mut stream: BoxReadWrite = self.open_sync_stream(host, port)?; - let method = self.get_method(); + let method: Method = self.get_method(); if method.is_get() { self.send_get_request_sync(&mut stream) } else if method.is_post() { self.send_post_request_sync(&mut stream) } else { - Err(RequestError::Request("Method Not Allowed".to_string())) + Err(RequestError::Request(METHOD_NOT_ALLOWED.to_string())) } } @@ -169,17 +169,17 @@ impl HttpRequest { /// /// - `RequestResult` - The parsed HTTP response, or a `RequestError` on failure. async fn send_async_impl(&mut self) -> RequestResult { - let url_obj = self.parse_url()?; + let url_obj: HttpUrlComponents = self.parse_url()?; let host: String = url_obj.host.clone().unwrap_or_default(); let port: u16 = self.resolve_port(url_obj.port.unwrap_or_default()); let mut stream: BoxAsyncReadWrite = self.open_async_stream(host, port).await?; - let method = self.get_method(); + let method: Method = self.get_method(); if method.is_get() { self.send_get_request_async(&mut stream).await } else if method.is_post() { self.send_post_request_async(&mut stream).await } else { - Err(RequestError::Request("Method Not Allowed".to_string())) + Err(RequestError::Request(METHOD_NOT_ALLOWED.to_string())) } } @@ -196,7 +196,7 @@ impl HttpRequest { if port != 0 { return port; } - let protocol = Self::protocol_lower(self.get_config_ref()); + let protocol: String = Self::protocol_lower(self.get_config_ref()); Protocol::get_port(&protocol) } @@ -225,21 +225,21 @@ impl HttpRequest { if let Some(proxy) = &self.get_config_ref().proxy { return self.open_sync_proxy_stream(host, port, proxy); } - let timeout = Duration::from_millis(self.get_config_ref().timeout); - let tcp = TcpStream::connect((host.clone(), port)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + let timeout: Duration = Duration::from_millis(self.get_config_ref().timeout); + let tcp: TcpStream = TcpStream::connect((host.clone(), port)) + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; tcp.set_read_timeout(Some(timeout)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; tcp.set_write_timeout(Some(timeout)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if self.is_https() { let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); - let tls_cfg = ClientConfig::builder() + let tls_cfg: ClientConfig = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); - let dns = ServerName::try_from(host.clone()) + let dns: ServerName<'static> = ServerName::try_from(host.clone()) .map_err(|e: InvalidDnsNameError| RequestError::Request(e.to_string()))?; - let session = ClientConnection::new(Arc::new(tls_cfg), dns) + let session: ClientConnection = ClientConnection::new(Arc::new(tls_cfg), dns) .map_err(|e: rustls::Error| RequestError::Request(e.to_string()))?; Ok(Box::new(StreamOwned::new(session, tcp))) } else { @@ -260,14 +260,14 @@ impl HttpRequest { &mut self, stream: &mut BoxReadWrite, ) -> Result { - let path = self.full_path(); - let header_bytes = self.header_bytes(0); - let version = self.get_config_ref().http_version.to_string(); - let request = build_http_request("GET", path, header_bytes, None, version); + let path: String = self.full_path(); + let header_bytes: Vec = self.header_bytes(0); + let version: String = self.get_config_ref().http_version.to_string(); + let request: Vec = build_http_request("GET", path, header_bytes, None, version); stream .write_all(&request) .and_then(|_: ()| stream.flush()) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; self.read_response_sync(stream) } @@ -284,15 +284,21 @@ impl HttpRequest { &mut self, stream: &mut BoxReadWrite, ) -> Result { - let body_bytes = self.body_bytes(); - let path = self.full_path(); - let header_bytes = self.header_bytes(body_bytes.len()); - let version = self.get_config_ref().http_version.to_string(); - let request = build_http_request("POST", path, header_bytes, Some(body_bytes), version); + let body_bytes: Vec = self.body_bytes(); + let path: String = self.full_path(); + let header_bytes: Vec = self.header_bytes(body_bytes.len()); + let version: String = self.get_config_ref().http_version.to_string(); + let request: Vec = build_http_request( + HTTP_METHOD_POST, + path, + header_bytes, + Some(body_bytes), + version, + ); stream .write_all(&request) .and_then(|_: ()| stream.flush()) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; self.read_response_sync(stream) } @@ -309,36 +315,37 @@ impl HttpRequest { &mut self, stream: &mut BoxReadWrite, ) -> Result { - let buffer_size = self.get_config_ref().buffer_size; - let mut buffer = vec![0u8; buffer_size]; + let buffer_size: usize = self.get_config_ref().buffer_size; + let mut buffer: Vec = vec![0u8; buffer_size]; let mut response_bytes: Vec = Vec::with_capacity(buffer_size.max(8192)); - let mut headers_done = false; - let mut content_length = 0usize; + let mut headers_done: bool = false; + let mut content_length: usize = 0usize; let mut redirect_url: Option> = None; - let mut headers_end_pos = 0usize; - let mut is_chunked = false; - let version_bytes = self + let mut headers_end_pos: usize = 0usize; + let mut is_chunked: bool = false; + let version_bytes: Vec = self .config .http_version .to_string() .to_ascii_lowercase() .into_bytes(); - let location_key = format!("{}:", LOCATION.to_ascii_lowercase()).into_bytes(); + let location_key: Vec = format!("{}:", LOCATION.to_ascii_lowercase()).into_bytes(); loop { - let n = stream + let n: usize = stream .read(&mut buffer) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if n == 0 { break; } - let new_cap = calculate_buffer_capacity(&response_bytes, n, response_bytes.capacity()); + let new_cap: usize = + calculate_buffer_capacity(&response_bytes, n, response_bytes.capacity()); if new_cap > 0 { response_bytes.reserve(new_cap - response_bytes.capacity()); } - let old_len = response_bytes.len(); + let old_len: usize = response_bytes.len(); response_bytes.extend_from_slice(&buffer[..n]); if !headers_done { - let search_start = old_len.saturating_sub(3); + let search_start: usize = old_len.saturating_sub(3); if let Some(pos) = find_double_crlf(&response_bytes, search_start) { headers_done = true; headers_end_pos = pos + 4; @@ -364,12 +371,12 @@ impl HttpRequest { } } if is_chunked { - let body_bytes = response_bytes[headers_end_pos..].to_vec(); - let decoded = parse_chunked_body(&body_bytes); + let body_bytes: Vec = response_bytes[headers_end_pos..].to_vec(); + let decoded: Vec = parse_chunked_body(&body_bytes); response_bytes.truncate(headers_end_pos); response_bytes.extend_from_slice(&decoded); } - let mut response = HttpResponse::from_bytes(&response_bytes); + let mut response: HttpResponse = HttpResponse::from_bytes(&response_bytes); if !self.get_config_ref().redirect || redirect_url.is_none() { if self.get_config_ref().decode { response = response.decode(self.get_config_ref().buffer_size); @@ -378,8 +385,8 @@ impl HttpRequest { } let url_bytes: Vec = redirect_url .take() - .ok_or_else(|| RequestError::Request("Missing Redirect URL".to_string()))?; - let url = String::from_utf8(url_bytes) + .ok_or_else(|| RequestError::Request(MISSING_REDIRECT_URL.to_string()))?; + let url: String = String::from_utf8(url_bytes) .map_err(|e: FromUtf8Error| RequestError::Request(e.to_string()))?; self.handle_redirect(url) } @@ -395,15 +402,15 @@ impl HttpRequest { /// - `Result` - The response of the redirect target, or a `RequestError` when redirects are disabled, looping, or exhausted. fn handle_redirect(&mut self, url: String) -> Result { if !self.get_config_ref().redirect { - return Err(RequestError::Request("Redirect Not Enabled".to_string())); + return Err(RequestError::Request(REDIRECT_NOT_ENABLED.to_string())); } if self.get_tmp_ref().get_visit_url().contains(&url) { - return Err(RequestError::Request("Redirect URL Dead Loop".to_string())); + return Err(RequestError::Request(REDIRECT_URL_DEAD_LOOP.to_string())); } self.get_tmp_mut().get_mut_visit_url().insert(url.clone()); if self.get_config_ref().max_redirect_times == 0 { return Err(RequestError::Request( - "Max Redirect Times Exceeded".to_string(), + MAX_REDIRECT_TIMES_EXCEEDED.to_string(), )); } self.get_config_mut().max_redirect_times -= 1; @@ -421,21 +428,21 @@ impl HttpRequest { /// /// - `bool` - `true` when the terminating chunk is present, `false` while more bytes are needed. fn is_chunked_response_complete(body_bytes: &[u8]) -> bool { - let mut pos = 0; + let mut pos: usize = 0; while pos < body_bytes.len() { - let chunk_size_end = match body_bytes[pos..] + let chunk_size_end: usize = match body_bytes[pos..] .windows(2) .position(|w: &[u8]| w == b"\r\n") { Some(p) => pos + p, None => return false, }; - let raw = &body_bytes[pos..chunk_size_end]; + let raw: &[u8] = &body_bytes[pos..chunk_size_end]; let chunk_size_str: &[u8] = match raw.iter().position(|&b: &u8| b == b';') { Some(p) => &raw[..p], None => raw, }; - let chunk_size: usize = match std::str::from_utf8(chunk_size_str) { + let chunk_size: usize = match from_utf8(chunk_size_str) { Ok(s) => match usize::from_str_radix(s.trim(), 16) { Ok(n) => n, Err(_) => return false, @@ -445,8 +452,8 @@ impl HttpRequest { if chunk_size == 0 { return true; } - let start = chunk_size_end + 2; - let end = start + chunk_size; + let start: usize = chunk_size_end + 2; + let end: usize = start + chunk_size; if end + 2 > body_bytes.len() { return false; } @@ -475,21 +482,22 @@ impl HttpRequest { if let Some(proxy) = &self.get_config_ref().proxy { return self.open_async_proxy_stream(host, port, proxy).await; } - let tcp = http_type::tokio::net::TcpStream::connect((host.clone(), port)) - .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + let tcp: http_type::tokio::net::TcpStream = + http_type::tokio::net::TcpStream::connect((host.clone(), port)) + .await + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if self.is_https() { let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); - let tls_cfg = ClientConfig::builder() + let tls_cfg: ClientConfig = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); - let connector = TlsConnector::from(Arc::new(tls_cfg)); - let dns = ServerName::try_from(host.clone()) + let connector: TlsConnector = TlsConnector::from(Arc::new(tls_cfg)); + let dns: ServerName<'static> = ServerName::try_from(host.clone()) .map_err(|e: InvalidDnsNameError| RequestError::Request(e.to_string()))?; - let tls = connector + let tls: tokio_rustls::client::TlsStream = connector .connect(dns, tcp) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; Ok(Box::new(tls)) } else { Ok(Box::new(tcp)) @@ -509,18 +517,18 @@ impl HttpRequest { &mut self, stream: &mut BoxAsyncReadWrite, ) -> Result { - let path = self.full_path(); - let header_bytes = self.header_bytes(0); - let version = self.get_config_ref().http_version.to_string(); - let request = build_http_request("GET", path, header_bytes, None, version); + let path: String = self.full_path(); + let header_bytes: Vec = self.header_bytes(0); + let version: String = self.get_config_ref().http_version.to_string(); + let request: Vec = build_http_request("GET", path, header_bytes, None, version); stream .write_all(&request) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; stream .flush() .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; self.read_response_async(stream).await } @@ -537,19 +545,25 @@ impl HttpRequest { &mut self, stream: &mut BoxAsyncReadWrite, ) -> Result { - let body_bytes = self.body_bytes(); - let path = self.full_path(); - let header_bytes = self.header_bytes(body_bytes.len()); - let version = self.get_config_ref().http_version.to_string(); - let request = build_http_request("POST", path, header_bytes, Some(body_bytes), version); + let body_bytes: Vec = self.body_bytes(); + let path: String = self.full_path(); + let header_bytes: Vec = self.header_bytes(body_bytes.len()); + let version: String = self.get_config_ref().http_version.to_string(); + let request: Vec = build_http_request( + HTTP_METHOD_POST, + path, + header_bytes, + Some(body_bytes), + version, + ); stream .write_all(&request) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; stream .flush() .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; self.read_response_async(stream).await } @@ -566,37 +580,38 @@ impl HttpRequest { &mut self, stream: &mut BoxAsyncReadWrite, ) -> Result { - let buffer_size = self.get_config_ref().buffer_size; - let mut buffer = vec![0u8; buffer_size]; + let buffer_size: usize = self.get_config_ref().buffer_size; + let mut buffer: Vec = vec![0u8; buffer_size]; let mut response_bytes: Vec = Vec::with_capacity(buffer_size.max(8192)); - let mut headers_done = false; - let mut content_length = 0usize; + let mut headers_done: bool = false; + let mut content_length: usize = 0usize; let mut redirect_url: Option> = None; - let mut headers_end_pos = 0usize; - let mut is_chunked = false; - let version_bytes = self + let mut headers_end_pos: usize = 0usize; + let mut is_chunked: bool = false; + let version_bytes: Vec = self .config .http_version .to_string() .to_ascii_lowercase() .into_bytes(); - let location_key = format!("{}:", LOCATION.to_ascii_lowercase()).into_bytes(); + let location_key: Vec = format!("{}:", LOCATION.to_ascii_lowercase()).into_bytes(); loop { - let n = stream + let n: usize = stream .read(&mut buffer) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if n == 0 { break; } - let new_cap = calculate_buffer_capacity(&response_bytes, n, response_bytes.capacity()); + let new_cap: usize = + calculate_buffer_capacity(&response_bytes, n, response_bytes.capacity()); if new_cap > 0 { response_bytes.reserve(new_cap - response_bytes.capacity()); } - let old_len = response_bytes.len(); + let old_len: usize = response_bytes.len(); response_bytes.extend_from_slice(&buffer[..n]); if !headers_done { - let search_start = old_len.saturating_sub(3); + let search_start: usize = old_len.saturating_sub(3); if let Some(pos) = find_double_crlf(&response_bytes, search_start) { headers_done = true; headers_end_pos = pos + 4; @@ -622,12 +637,12 @@ impl HttpRequest { } } if is_chunked { - let body_bytes = response_bytes[headers_end_pos..].to_vec(); - let decoded = parse_chunked_body(&body_bytes); + let body_bytes: Vec = response_bytes[headers_end_pos..].to_vec(); + let decoded: Vec = parse_chunked_body(&body_bytes); response_bytes.truncate(headers_end_pos); response_bytes.extend_from_slice(&decoded); } - let mut response = HttpResponse::from_bytes(&response_bytes); + let mut response: HttpResponse = HttpResponse::from_bytes(&response_bytes); if !self.get_config_ref().redirect || redirect_url.is_none() { if self.get_config_ref().decode { response = response.decode(self.get_config_ref().buffer_size); @@ -636,8 +651,8 @@ impl HttpRequest { } let url_bytes: Vec = redirect_url .take() - .ok_or_else(|| RequestError::Request("Missing Redirect URL".to_string()))?; - let url = String::from_utf8(url_bytes) + .ok_or_else(|| RequestError::Request(MISSING_REDIRECT_URL.to_string()))?; + let url: String = String::from_utf8(url_bytes) .map_err(|e: FromUtf8Error| RequestError::Request(e.to_string()))?; self.handle_redirect_async(url).await } @@ -653,15 +668,15 @@ impl HttpRequest { /// - `Result` - The response of the redirect target, or a `RequestError` when redirects are disabled, looping, or exhausted. async fn handle_redirect_async(&mut self, url: String) -> Result { if !self.get_config_ref().redirect { - return Err(RequestError::Request("Redirect Not Enabled".to_string())); + return Err(RequestError::Request(REDIRECT_NOT_ENABLED.to_string())); } if self.get_tmp_ref().get_visit_url().contains(&url) { - return Err(RequestError::Request("Redirect URL Dead Loop".to_string())); + return Err(RequestError::Request(REDIRECT_URL_DEAD_LOOP.to_string())); } self.get_tmp_mut().get_mut_visit_url().insert(url.clone()); if self.get_config_ref().max_redirect_times == 0 { return Err(RequestError::Request( - "Max Redirect Times Exceeded".to_string(), + MAX_REDIRECT_TIMES_EXCEEDED.to_string(), )); } self.get_config_mut().max_redirect_times -= 1; @@ -713,21 +728,21 @@ impl HttpRequest { target_port: u16, proxy: &Proxy, ) -> Result { - let timeout = Duration::from_millis(self.get_config_ref().timeout); - let tcp = TcpStream::connect((proxy.host.clone(), proxy.port)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + let timeout: Duration = Duration::from_millis(self.get_config_ref().timeout); + let tcp: TcpStream = TcpStream::connect((proxy.host.clone(), proxy.port)) + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; tcp.set_read_timeout(Some(timeout)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; tcp.set_write_timeout(Some(timeout)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; let mut proxy_stream: BoxReadWrite = if proxy.proxy_type == ProxyType::Https { let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); - let tls_cfg = ClientConfig::builder() + let tls_cfg: ClientConfig = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); - let dns = ServerName::try_from(proxy.host.clone()) + let dns: ServerName<'static> = ServerName::try_from(proxy.host.clone()) .map_err(|e: InvalidDnsNameError| RequestError::Request(e.to_string()))?; - let session = ClientConnection::new(Arc::new(tls_cfg), dns) + let session: ClientConnection = ClientConnection::new(Arc::new(tls_cfg), dns) .map_err(|e: rustls::Error| RequestError::Request(e.to_string()))?; Box::new(StreamOwned::new(session, tcp)) } else { @@ -735,8 +750,8 @@ impl HttpRequest { }; let connect_request: String = if let (Some(u), Some(p)) = (&proxy.username, &proxy.password) { - let auth = format!("{u}:{p}"); - let encoded = crate::utils::base64_encode(auth.as_bytes()); + let auth: String = format!("{u}:{p}"); + let encoded: String = crate::utils::base64_encode(auth.as_bytes()); format!( "CONNECT {target_host}:{target_port} HTTP/1.1\r\nHost: {target_host}:{target_port}\r\nProxy-Authorization: Basic {encoded}\r\n\r\n" ) @@ -747,38 +762,39 @@ impl HttpRequest { }; proxy_stream .write_all(connect_request.as_bytes()) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; proxy_stream .flush() - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut buf = [0u8; 1024]; - let n = proxy_stream + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut buf: [u8; 1024] = [0u8; 1024]; + let n: usize = proxy_stream .read(&mut buf) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let s = std::str::from_utf8(&buf[..n]).unwrap_or(""); - let pre_read = if let Some(pos) = s.find("\r\n\r\n") { - let header_part = &s[..pos]; - if !header_part.starts_with("HTTP/1.1 200") && !header_part.starts_with("HTTP/1.0 200") + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let s: &str = from_utf8(&buf[..n]).unwrap_or(""); + let pre_read: Vec = if let Some(pos) = s.find(HEADER_TERMINATOR) { + let header_part: &str = &s[..pos]; + if !header_part.starts_with(HTTP_1_1_OK_PREFIX) + && !header_part.starts_with(HTTP_1_0_OK_PREFIX) { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } buf[pos + 4..n].to_vec() } else { - if !s.starts_with("HTTP/1.1 200") && !s.starts_with("HTTP/1.0 200") { - return Err(RequestError::Request("Internal Server Error".to_string())); + if !s.starts_with(HTTP_1_1_OK_PREFIX) && !s.starts_with(HTTP_1_0_OK_PREFIX) { + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } Vec::new() }; if self.is_https() { let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); - let tls_cfg = ClientConfig::builder() + let tls_cfg: ClientConfig = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); - let dns = ServerName::try_from(target_host.clone()) + let dns: ServerName<'static> = ServerName::try_from(target_host.clone()) .map_err(|e: InvalidDnsNameError| RequestError::Request(e.to_string()))?; - let session = ClientConnection::new(Arc::new(tls_cfg), dns) + let session: ClientConnection = ClientConnection::new(Arc::new(tls_cfg), dns) .map_err(|e: rustls::Error| RequestError::Request(e.to_string()))?; - let tunnel = SyncProxyTunnelStream::new(proxy_stream, pre_read); + let tunnel: SyncProxyTunnelStream = SyncProxyTunnelStream::new(proxy_stream, pre_read); return Ok(Box::new(StreamOwned::new(session, tunnel))); } Ok(Box::new(SyncProxyTunnelStream::new(proxy_stream, pre_read))) @@ -801,47 +817,47 @@ impl HttpRequest { target_port: u16, proxy: &Proxy, ) -> Result { - let timeout = Duration::from_millis(self.get_config_ref().timeout); - let mut tcp = TcpStream::connect((proxy.host.clone(), proxy.port)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + let timeout: Duration = Duration::from_millis(self.get_config_ref().timeout); + let mut tcp: TcpStream = TcpStream::connect((proxy.host.clone(), proxy.port)) + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; tcp.set_read_timeout(Some(timeout)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; tcp.set_write_timeout(Some(timeout)) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; let auth_methods: Vec = if proxy.username.is_some() && proxy.password.is_some() { vec![0x05, 0x02, 0x00, 0x02] } else { vec![0x05, 0x01, 0x00] }; tcp.write_all(&auth_methods) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut resp = [0u8; 2]; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut resp: [u8; 2] = [0u8; 2]; tcp.read_exact(&mut resp) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if resp[0] != 0x05 { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } match resp[1] { 0x00 => {} 0x02 => { let (Some(u), Some(p)) = (&proxy.username, &proxy.password) else { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); }; - let mut auth_req = vec![0x01]; + let mut auth_req: Vec = vec![0x01]; auth_req.push(u.len() as u8); auth_req.extend_from_slice(u.as_bytes()); auth_req.push(p.len() as u8); auth_req.extend_from_slice(p.as_bytes()); tcp.write_all(&auth_req) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut auth_resp = [0u8; 2]; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut auth_resp: [u8; 2] = [0u8; 2]; tcp.read_exact(&mut auth_resp) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if auth_resp[1] != 0x00 { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } } - _ => return Err(RequestError::Request("Internal Server Error".to_string())), + _ => return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())), } let mut connect_req: Vec = vec![0x05, 0x01, 0x00]; if let Ok(ip) = target_host.parse::() { @@ -857,45 +873,45 @@ impl HttpRequest { } connect_req.extend_from_slice(&target_port.to_be_bytes()); tcp.write_all(&connect_req) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut connect_resp = [0u8; 4]; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut connect_resp: [u8; 4] = [0u8; 4]; tcp.read_exact(&mut connect_resp) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if connect_resp[0] != 0x05 || connect_resp[1] != 0x00 { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } match connect_resp[3] { 0x01 => { - let mut skip = [0u8; 6]; + let mut skip: [u8; 6] = [0u8; 6]; tcp.read_exact(&mut skip) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; } 0x03 => { - let mut len = [0u8; 1]; + let mut len: [u8; 1] = [0u8; 1]; tcp.read_exact(&mut len) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut skip = vec![0u8; len[0] as usize + 2]; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut skip: Vec = vec![0u8; len[0] as usize + 2]; tcp.read_exact(&mut skip) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; } 0x04 => { - let mut skip = [0u8; 18]; + let mut skip: [u8; 18] = [0u8; 18]; tcp.read_exact(&mut skip) - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; } - _ => return Err(RequestError::Request("Internal Server Error".to_string())), + _ => return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())), } if self.is_https() { let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); - let tls_cfg = ClientConfig::builder() + let tls_cfg: ClientConfig = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); - let dns = ServerName::try_from(target_host) + let dns: ServerName<'static> = ServerName::try_from(target_host) .map_err(|e: InvalidDnsNameError| RequestError::Request(e.to_string()))?; - let session = ClientConnection::new(Arc::new(tls_cfg), dns) + let session: ClientConnection = ClientConnection::new(Arc::new(tls_cfg), dns) .map_err(|e: rustls::Error| RequestError::Request(e.to_string()))?; let proxy_box: BoxReadWrite = Box::new(tcp); - let tunnel = SyncProxyTunnelStream::new(proxy_box, Vec::new()); + let tunnel: SyncProxyTunnelStream = SyncProxyTunnelStream::new(proxy_box, Vec::new()); return Ok(Box::new(StreamOwned::new(session, tunnel))); } Ok(Box::new(tcp)) @@ -949,29 +965,30 @@ impl HttpRequest { target_port: u16, proxy: &Proxy, ) -> Result { - let tcp = http_type::tokio::net::TcpStream::connect((proxy.host.clone(), proxy.port)) - .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + let tcp: http_type::tokio::net::TcpStream = + http_type::tokio::net::TcpStream::connect((proxy.host.clone(), proxy.port)) + .await + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; let mut proxy_stream: BoxAsyncReadWrite = if proxy.proxy_type == ProxyType::Https { let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); - let tls_cfg = ClientConfig::builder() + let tls_cfg: ClientConfig = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); - let connector = TlsConnector::from(Arc::new(tls_cfg)); - let dns = ServerName::try_from(proxy.host.clone()) + let connector: TlsConnector = TlsConnector::from(Arc::new(tls_cfg)); + let dns: ServerName<'static> = ServerName::try_from(proxy.host.clone()) .map_err(|e: InvalidDnsNameError| RequestError::Request(e.to_string()))?; - let tls = connector + let tls: tokio_rustls::client::TlsStream = connector .connect(dns, tcp) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; Box::new(tls) } else { Box::new(tcp) }; let connect_request: String = if let (Some(u), Some(p)) = (&proxy.username, &proxy.password) { - let auth = format!("{u}:{p}"); - let encoded = crate::utils::base64_encode(auth.as_bytes()); + let auth: String = format!("{u}:{p}"); + let encoded: String = crate::utils::base64_encode(auth.as_bytes()); format!( "CONNECT {target_host}:{target_port} HTTP/1.1\r\nHost: {target_host}:{target_port}\r\nProxy-Authorization: Basic {encoded}\r\n\r\n" ) @@ -983,43 +1000,44 @@ impl HttpRequest { proxy_stream .write_all(connect_request.as_bytes()) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; proxy_stream .flush() .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut buf = [0u8; 1024]; - let n = proxy_stream + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut buf: [u8; 1024] = [0u8; 1024]; + let n: usize = proxy_stream .read(&mut buf) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let s = std::str::from_utf8(&buf[..n]).unwrap_or(""); - let pre_read = if let Some(pos) = s.find("\r\n\r\n") { - let header_part = &s[..pos]; - if !header_part.starts_with("HTTP/1.1 200") && !header_part.starts_with("HTTP/1.0 200") + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let s: &str = from_utf8(&buf[..n]).unwrap_or(""); + let pre_read: Vec = if let Some(pos) = s.find(HEADER_TERMINATOR) { + let header_part: &str = &s[..pos]; + if !header_part.starts_with(HTTP_1_1_OK_PREFIX) + && !header_part.starts_with(HTTP_1_0_OK_PREFIX) { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } buf[pos + 4..n].to_vec() } else { - if !s.starts_with("HTTP/1.1 200") && !s.starts_with("HTTP/1.0 200") { - return Err(RequestError::Request("Internal Server Error".to_string())); + if !s.starts_with(HTTP_1_1_OK_PREFIX) && !s.starts_with(HTTP_1_0_OK_PREFIX) { + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } Vec::new() }; if self.is_https() { let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); - let tls_cfg = ClientConfig::builder() + let tls_cfg: ClientConfig = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); - let connector = TlsConnector::from(Arc::new(tls_cfg)); - let dns = ServerName::try_from(target_host.clone()) + let connector: TlsConnector = TlsConnector::from(Arc::new(tls_cfg)); + let dns: ServerName<'static> = ServerName::try_from(target_host.clone()) .map_err(|e: InvalidDnsNameError| RequestError::Request(e.to_string()))?; - let tunnel = ProxyTunnelStream::new(proxy_stream, pre_read); - let tls = connector + let tunnel: ProxyTunnelStream = ProxyTunnelStream::new(proxy_stream, pre_read); + let tls: tokio_rustls::client::TlsStream = connector .connect(dns, tunnel) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; Ok(Box::new(tls)) } else { Ok(Box::new(ProxyTunnelStream::new(proxy_stream, pre_read))) @@ -1043,9 +1061,10 @@ impl HttpRequest { target_port: u16, proxy: &Proxy, ) -> Result { - let mut tcp = http_type::tokio::net::TcpStream::connect((proxy.host.clone(), proxy.port)) - .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + let mut tcp: http_type::tokio::net::TcpStream = + http_type::tokio::net::TcpStream::connect((proxy.host.clone(), proxy.port)) + .await + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; let auth_methods: Vec = if proxy.username.is_some() && proxy.password.is_some() { vec![0x05, 0x02, 0x00, 0x02] } else { @@ -1053,37 +1072,37 @@ impl HttpRequest { }; tcp.write_all(&auth_methods) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut resp = [0u8; 2]; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut resp: [u8; 2] = [0u8; 2]; tcp.read_exact(&mut resp) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if resp[0] != 0x05 { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } match resp[1] { 0x00 => {} 0x02 => { let (Some(u), Some(p)) = (&proxy.username, &proxy.password) else { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); }; - let mut auth_req = vec![0x01u8]; + let mut auth_req: Vec = vec![0x01u8]; auth_req.push(u.len() as u8); auth_req.extend_from_slice(u.as_bytes()); auth_req.push(p.len() as u8); auth_req.extend_from_slice(p.as_bytes()); tcp.write_all(&auth_req) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut auth_resp = [0u8; 2]; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut auth_resp: [u8; 2] = [0u8; 2]; tcp.read_exact(&mut auth_resp) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if auth_resp[1] != 0x00 { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } } - _ => return Err(RequestError::Request("Internal Server Error".to_string())), + _ => return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())), } let mut connect_req: Vec = vec![0x05, 0x01, 0x00]; if let Ok(ip) = target_host.parse::() { @@ -1100,53 +1119,53 @@ impl HttpRequest { connect_req.extend_from_slice(&target_port.to_be_bytes()); tcp.write_all(&connect_req) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut connect_resp = [0u8; 4]; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut connect_resp: [u8; 4] = [0u8; 4]; tcp.read_exact(&mut connect_resp) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; if connect_resp[0] != 0x05 || connect_resp[1] != 0x00 { - return Err(RequestError::Request("Internal Server Error".to_string())); + return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())); } match connect_resp[3] { 0x01 => { - let mut skip = [0u8; 6]; + let mut skip: [u8; 6] = [0u8; 6]; tcp.read_exact(&mut skip) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; } 0x03 => { - let mut len = [0u8; 1]; + let mut len: [u8; 1] = [0u8; 1]; tcp.read_exact(&mut len) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; - let mut skip = vec![0u8; len[0] as usize + 2]; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; + let mut skip: Vec = vec![0u8; len[0] as usize + 2]; tcp.read_exact(&mut skip) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; } 0x04 => { - let mut skip = [0u8; 18]; + let mut skip: [u8; 18] = [0u8; 18]; tcp.read_exact(&mut skip) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; } - _ => return Err(RequestError::Request("Internal Server Error".to_string())), + _ => return Err(RequestError::Request(INTERNAL_SERVER_ERROR.to_string())), } if self.is_https() { let roots: RootCertStore = self.get_tmp_ref().get_root_cert().clone(); - let tls_cfg = ClientConfig::builder() + let tls_cfg: ClientConfig = ClientConfig::builder() .with_root_certificates(roots) .with_no_client_auth(); - let connector = TlsConnector::from(Arc::new(tls_cfg)); - let dns = ServerName::try_from(target_host) + let connector: TlsConnector = TlsConnector::from(Arc::new(tls_cfg)); + let dns: ServerName<'static> = ServerName::try_from(target_host) .map_err(|e: InvalidDnsNameError| RequestError::Request(e.to_string()))?; let proxy_box: BoxAsyncReadWrite = Box::new(tcp); - let tunnel = ProxyTunnelStream::new(proxy_box, Vec::new()); - let tls = connector + let tunnel: ProxyTunnelStream = ProxyTunnelStream::new(proxy_box, Vec::new()); + let tls: tokio_rustls::client::TlsStream = connector .connect(dns, tunnel) .await - .map_err(|e: std::io::Error| RequestError::Request(e.to_string()))?; + .map_err(|e: io::Error| RequestError::Request(e.to_string()))?; Ok(Box::new(tls)) } else { Ok(Box::new(tcp)) @@ -1227,7 +1246,7 @@ impl HttpRequest { /// /// - `&mut Self` - The request itself, for chaining. pub fn set_header, V: AsRef>(&mut self, key: K, value: V) -> &mut Self { - let normalized = Self::normalize_header_key(key.as_ref()); + let normalized: String = Self::normalize_header_key(key.as_ref()); self.headers.insert(normalized, value.as_ref().to_owned()); self } @@ -1242,7 +1261,7 @@ impl HttpRequest { /// /// - `&mut Self` - The request itself, for chaining. pub fn remove_header>(&mut self, key: K) -> &mut Self { - let normalized = Self::normalize_header_key(key.as_ref()); + let normalized: String = Self::normalize_header_key(key.as_ref()); self.get_mut_headers().remove(&normalized); self } diff --git a/request/src/request/http_request/trait.rs b/request/src/request/http_request/trait.rs index 502d597f..273d4607 100644 --- a/request/src/request/http_request/trait.rs +++ b/request/src/request/http_request/trait.rs @@ -11,8 +11,8 @@ pub trait AsyncRequestTrait: Send + Debug { /// /// # Returns /// - /// - `Pin + Send + '_>>` - - /// A pinned boxed future representing the asynchronous operation. + /// - `Pin + Send + '_>>`: a pinned + /// boxed future representing the asynchronous operation. fn send(&mut self) -> Pin + Send + '_>>; } diff --git a/request/src/request/parser/fn.rs b/request/src/request/parser/fn.rs index f4d221c6..3b19cf1f 100644 --- a/request/src/request/parser/fn.rs +++ b/request/src/request/parser/fn.rs @@ -2,6 +2,15 @@ use super::*; /// Split on a multi-byte delimiter, returning each slice. Used to split an /// HTTP response into status line / headers / body. +/// +/// # Arguments +/// +/// - `&'a [u8]` - The buffer to split. +/// - `&'a [u8]` - The multi-byte delimiter to split on. +/// +/// # Returns +/// +/// - `Vec<&'a [u8]>`: The delimiter-separated slices, including any trailing remainder. pub(crate) fn split_multi_byte<'a>(data: &'a [u8], delimiter: &'a [u8]) -> Vec<&'a [u8]> { let mut result: Vec<&[u8]> = Vec::new(); let mut start: usize = 0; @@ -18,6 +27,14 @@ pub(crate) fn split_multi_byte<'a>(data: &'a [u8], delimiter: &'a [u8]) -> Vec<& } /// Split on whitespace (space or tab). +/// +/// # Arguments +/// +/// - `&[u8]` - The buffer to split. +/// +/// # Returns +/// +/// - `Vec<&[u8]>`: The non-empty whitespace-separated slices. pub(crate) fn split_whitespace(input: &[u8]) -> Vec<&[u8]> { let mut parts: Vec<&[u8]> = Vec::new(); let mut start: usize = 0; @@ -37,6 +54,18 @@ pub(crate) fn split_whitespace(input: &[u8]) -> Vec<&[u8]> { /// Build the raw HTTP request line + headers + optional body into a single /// `Vec` suitable for writing to the wire. +/// +/// # Arguments +/// +/// - `&str` - The HTTP method token. +/// - `String` - The request path, with any query string already appended. +/// - `Vec` - The wire-format header block bytes. +/// - `Option>` - The wire-format body bytes, or `None` when there is no body. +/// - `String` - The HTTP version token. +/// +/// # Returns +/// +/// - `Vec`: The complete request bytes, ready to write to the wire. pub(crate) fn build_http_request( method: &str, path: String, @@ -66,6 +95,14 @@ pub(crate) fn build_http_request( /// /// Walks the `chunk-size CRLF chunk-data CRLF` sequence until it sees a /// terminating zero-size chunk. +/// +/// # Arguments +/// +/// - `&[u8]` - The raw chunked body bytes. +/// +/// # Returns +/// +/// - `Vec`: The concatenated payload of every decoded chunk. pub(crate) fn parse_chunked_body(body_bytes: &[u8]) -> Vec { let mut result: Vec = Vec::new(); let mut pos: usize = 0; @@ -82,7 +119,7 @@ pub(crate) fn parse_chunked_body(body_bytes: &[u8]) -> Vec { Some(p) => &chunk_size_str[..p], None => chunk_size_str, }; - let chunk_size: usize = match std::str::from_utf8(chunk_size_str) { + let chunk_size: usize = match from_utf8(chunk_size_str) { Ok(s) => match usize::from_str_radix(s.trim(), 16) { Ok(n) => n, Err(_) => break, @@ -104,6 +141,15 @@ pub(crate) fn parse_chunked_body(body_bytes: &[u8]) -> Vec { } /// Locate `\r\n\r\n` (end of response headers) starting at `start`. +/// +/// # Arguments +/// +/// - `&[u8]` - The buffer to search. +/// - `usize` - The offset at which the search starts. +/// +/// # Returns +/// +/// - `Option`: The absolute offset of the terminator, or `None` when absent. pub(crate) fn find_double_crlf(data: &[u8], start: usize) -> Option { let search_data: &[u8] = &data[start..]; for i in 0..search_data.len().saturating_sub(3) { @@ -119,6 +165,15 @@ pub(crate) fn find_double_crlf(data: &[u8], start: usize) -> Option { } /// Find a byte-pattern in a haystack, ASCII-case-insensitive. +/// +/// # Arguments +/// +/// - `&[u8]` - The haystack to search. +/// - `&[u8]` - The byte pattern to look for. +/// +/// # Returns +/// +/// - `Option`: The offset of the first match, or `None` when absent. pub(crate) fn find_pattern_case_insensitive(haystack: &[u8], needle: &[u8]) -> Option { if needle.is_empty() || haystack.len() < needle.len() { return None; @@ -141,6 +196,15 @@ pub(crate) fn find_pattern_case_insensitive(haystack: &[u8], needle: &[u8]) -> O } /// Locate the next `\r\n` after `start`. +/// +/// # Arguments +/// +/// - `&[u8]` - The buffer to search. +/// - `usize` - The offset at which the search starts. +/// +/// # Returns +/// +/// - `Option`: The absolute offset of the terminator, or `None` when absent. pub(crate) fn find_crlf(data: &[u8], start: usize) -> Option { let search_data: &[u8] = &data[start..]; for i in 0..search_data.len().saturating_sub(1) { @@ -152,6 +216,14 @@ pub(crate) fn find_crlf(data: &[u8], start: usize) -> Option { } /// Extract `Content-Length` value from response bytes (0 if missing). +/// +/// # Arguments +/// +/// - `&[u8]` - The raw response header bytes. +/// +/// # Returns +/// +/// - `usize`: The declared body length, or `0` when the header is missing. pub(crate) fn get_content_length(response_bytes: &[u8]) -> usize { if let Some(pos) = find_pattern_case_insensitive(response_bytes, CONTENT_LENGTH_PATTERN) { let value_start: usize = pos + CONTENT_LENGTH_PATTERN.len(); @@ -169,6 +241,14 @@ pub(crate) fn get_content_length(response_bytes: &[u8]) -> usize { } /// Does the response use `Transfer-Encoding: chunked`? +/// +/// # Arguments +/// +/// - `&[u8]` - The raw response header bytes. +/// +/// # Returns +/// +/// - `bool`: `true` when the body is chunked, `false` otherwise. pub(crate) fn is_chunked_encoding(headers_bytes: &[u8]) -> bool { if let Some(pos) = find_pattern_case_insensitive(headers_bytes, TRANSFER_ENCODING_PATTERN) { let value_start: usize = pos + TRANSFER_ENCODING_PATTERN.len(); @@ -186,6 +266,14 @@ pub(crate) fn is_chunked_encoding(headers_bytes: &[u8]) -> bool { } /// Parse a byte slice as a decimal `usize` (skipping leading whitespace). +/// +/// # Arguments +/// +/// - `&[u8]` - The ASCII digits to parse. +/// +/// # Returns +/// +/// - `usize`: The parsed value, truncated at the first non-digit byte. pub(crate) fn parse_decimal_bytes(bytes: &[u8]) -> usize { let mut result: usize = 0; let mut started: bool = false; @@ -203,6 +291,14 @@ pub(crate) fn parse_decimal_bytes(bytes: &[u8]) -> usize { } /// Parse the 3-byte ASCII status code from the response status line. +/// +/// # Arguments +/// +/// - `&[u8]` - The status-line bytes following the HTTP version. +/// +/// # Returns +/// +/// - `usize`: The status code, or `0` when the slice is not three digits. pub(crate) fn parse_status_code(status_bytes: &[u8]) -> usize { if status_bytes.len() != 3 { return 0; @@ -219,6 +315,16 @@ pub(crate) fn parse_status_code(status_bytes: &[u8]) -> usize { } /// Calculate the next buffer capacity for growing the response buffer. +/// +/// # Arguments +/// +/// - `&[u8]` - The response bytes accumulated so far. +/// - `usize` - The number of bytes just read. +/// - `usize` - The current capacity of the response buffer. +/// +/// # Returns +/// +/// - `usize`: The new capacity to reserve, or `0` when no growth is needed. pub(crate) fn calculate_buffer_capacity( response_bytes: &[u8], n: usize, @@ -242,6 +348,19 @@ pub(crate) fn calculate_buffer_capacity( /// the `Content-Length`, the redirect URL (if any), and whether the body is /// chunked. All three out-params are populated; only `redirect_url` may be /// `None`. +/// +/// # Arguments +/// +/// - `&[u8]` - The raw response header bytes. +/// - `&[u8]` - The lower-cased HTTP version bytes used to locate the status line. +/// - `&[u8]` - The lower-cased `Location` header name, including its colon. +/// - `&mut usize` - Receives the declared content length. +/// - `&mut Option>` - Receives the redirect target, or stays `None`. +/// - `&mut bool` - Receives whether the body is chunked. +/// +/// # Returns +/// +/// - `Result<(), RequestError>`: `Ok(())` once the out-parameters are populated. pub(crate) fn parse_response_headers( headers_bytes: &[u8], http_version_bytes: &[u8], @@ -263,7 +382,7 @@ pub(crate) fn parse_response_headers( { let start: usize = location_pos + location_sign_key.len(); if let Some(end_pos) = find_crlf(headers_bytes, start) { - let mut url_vec = Vec::with_capacity(end_pos - start); + let mut url_vec: Vec = Vec::with_capacity(end_pos - start); url_vec.extend_from_slice(&headers_bytes[start..end_pos]); *redirect_url = Some(url_vec); } diff --git a/request/src/request/proxy/impl.rs b/request/src/request/proxy/impl.rs index 302a165e..3fb57927 100644 --- a/request/src/request/proxy/impl.rs +++ b/request/src/request/proxy/impl.rs @@ -26,14 +26,14 @@ impl AsyncRead for ProxyTunnelStream { /// /// # Returns /// - /// - `Poll>` - The poll outcome carrying any read error. + /// - `Poll>` - The poll outcome carrying any read error. fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, - ) -> Poll> { + ) -> Poll> { if !self.get_pre_read_data().is_empty() { - let len: usize = std::cmp::min(self.get_pre_read_data().len(), buf.remaining()); + let len: usize = cmp::min(self.get_pre_read_data().len(), buf.remaining()); buf.put_slice(&self.get_pre_read_data()[..len]); self.get_mut_pre_read_data().drain(..len); return Poll::Ready(Ok(())); @@ -53,12 +53,12 @@ impl AsyncWrite for ProxyTunnelStream { /// /// # Returns /// - /// - `Poll>` - The poll outcome carrying the written byte count or any error. + /// - `Poll>` - The poll outcome carrying the written byte count or any error. fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], - ) -> Poll> { + ) -> Poll> { Pin::new(self.get_mut_inner()).poll_write(cx, buf) } @@ -71,11 +71,8 @@ impl AsyncWrite for ProxyTunnelStream { /// /// # Returns /// - /// - `Poll>` - The poll outcome carrying any flush error. - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + /// - `Poll>` - The poll outcome carrying any flush error. + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(self.get_mut_inner()).poll_flush(cx) } @@ -88,11 +85,11 @@ impl AsyncWrite for ProxyTunnelStream { /// /// # Returns /// - /// - `Poll>` - The poll outcome carrying any shutdown error. + /// - `Poll>` - The poll outcome carrying any shutdown error. fn poll_shutdown( mut self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll> { + ) -> Poll> { Pin::new(self.get_mut_inner()).poll_shutdown(cx) } } @@ -123,10 +120,10 @@ impl Read for SyncProxyTunnelStream { /// /// # Returns /// - /// - `std::io::Result` - The read outcome carrying the number of bytes read or any error. - fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + /// - `io::Result` - The read outcome carrying the number of bytes read or any error. + fn read(&mut self, buf: &mut [u8]) -> io::Result { if !self.get_pre_read_data().is_empty() { - let len: usize = std::cmp::min(self.get_pre_read_data().len(), buf.len()); + let len: usize = cmp::min(self.get_pre_read_data().len(), buf.len()); buf[..len].copy_from_slice(&self.get_pre_read_data()[..len]); self.get_mut_pre_read_data().drain(..len); return Ok(len); @@ -144,8 +141,8 @@ impl Write for SyncProxyTunnelStream { /// /// # Returns /// - /// - `std::io::Result` - The write outcome carrying the number of bytes written or any error. - fn write(&mut self, buf: &[u8]) -> std::io::Result { + /// - `io::Result` - The write outcome carrying the number of bytes written or any error. + fn write(&mut self, buf: &[u8]) -> io::Result { self.get_mut_inner().write(buf) } @@ -153,8 +150,8 @@ impl Write for SyncProxyTunnelStream { /// /// # Returns /// - /// - `std::io::Result<()>` - The flush outcome carrying any flush error. - fn flush(&mut self) -> std::io::Result<()> { + /// - `io::Result<()>` - The flush outcome carrying any flush error. + fn flush(&mut self) -> io::Result<()> { self.get_mut_inner().flush() } } diff --git a/request/src/request/request_builder/impl.rs b/request/src/request/request_builder/impl.rs index fd92f8eb..2683e575 100644 --- a/request/src/request/request_builder/impl.rs +++ b/request/src/request/request_builder/impl.rs @@ -323,6 +323,6 @@ impl RequestBuilder { /// /// - `HttpRequest` - The finalised request, leaving the builder empty. pub fn build(&mut self) -> HttpRequest { - std::mem::take(self.get_mut_request()) + mem::take(self.get_mut_request()) } } diff --git a/request/src/request/tmp/struct.rs b/request/src/request/tmp/struct.rs index b9e4b708..9431d86b 100644 --- a/request/src/request/tmp/struct.rs +++ b/request/src/request/tmp/struct.rs @@ -20,6 +20,11 @@ pub struct Tmp { } impl Default for Tmp { + /// Builds a default temporary store seeded with the bundled TLS server roots. + /// + /// # Returns + /// + /// - `Tmp`: a store with an empty visited-URL set and the bundled server roots. #[inline(always)] fn default() -> Self { Self { diff --git a/request/src/response/impl.rs b/request/src/response/impl.rs index c9c50d8b..32a6f024 100644 --- a/request/src/response/impl.rs +++ b/request/src/response/impl.rs @@ -153,7 +153,7 @@ impl HttpResponse { /// /// - `Option<&str>` - The header value, or `None` when the header is absent. pub fn get_header>(&self, key: K) -> Option<&str> { - let normalized = key.as_ref().to_ascii_lowercase(); + let normalized: String = key.as_ref().to_ascii_lowercase(); self.headers.get(&normalized).map(String::as_str) } diff --git a/request/tests/body/fn.rs b/request/tests/body/fn.rs new file mode 100644 index 00000000..86f35140 --- /dev/null +++ b/request/tests/body/fn.rs @@ -0,0 +1,69 @@ +use super::*; + +#[test] +fn test_body_empty_has_no_bytes() { + let body: Body = Body::empty(); + assert!(body.get_bytes_ref().is_empty()); + assert!(body.as_slice().is_empty()); + assert!(body.get_bytes().is_empty()); + assert_eq!(body.as_str(), Some("")); +} + +#[test] +fn test_body_default_equals_empty() { + let body: Body = Body::default(); + assert_eq!(body, Body::empty()); + assert!(body.get_bytes_ref().is_empty()); +} + +#[test] +fn test_body_from_bytes_accepts_str_and_vec() { + let from_str: Body = Body::from_bytes("hi"); + let from_vec: Body = Body::from_bytes(vec![b'h', b'i']); + assert_eq!(from_str, from_vec); + assert_eq!(from_str.get_bytes_ref(), from_vec.get_bytes_ref()); + assert_eq!(from_str.as_str(), Some("hi")); + assert_eq!(from_str.get_bytes_ref().len(), 2usize); +} + +#[test] +fn test_body_from_bytes_accepts_slice() { + let body: Body = Body::from_bytes(&[1u8, 2, 3][..]); + assert_eq!(body.get_bytes_ref(), &[1u8, 2, 3]); + assert_eq!(body.as_str(), Some("\u{1}\u{2}\u{3}")); +} + +#[test] +fn test_body_as_str_is_none_for_invalid_utf8() { + let body: Body = Body::from_bytes(vec![0xffu8, 0xfe]); + assert_eq!(body.as_str(), None); + assert_eq!(body.as_slice().len(), 2usize); + assert_eq!(body.get_bytes_ref(), &[0xffu8, 0xfe]); +} + +#[test] +fn test_body_display_writes_utf8_text() { + let body: Body = Body::from_bytes("hello"); + assert_eq!(format!("{body}"), "hello"); +} + +#[test] +fn test_body_display_falls_back_to_debug_bytes() { + let body: Body = Body::from_bytes(vec![0xffu8, 0xfe]); + assert_eq!(format!("{body}"), "[255, 254]"); +} + +#[test] +fn test_body_clone_is_equal_and_independent() { + let body: Body = Body::from_bytes("abc"); + let cloned: Body = body.clone(); + assert_eq!(body, cloned); + assert_eq!(cloned.get_bytes_ref(), b"abc"); +} + +#[test] +fn test_body_serializes_bytes_field() { + let body: Body = Body::from_bytes("hi"); + let encoded: String = serde_json::to_string(&body).unwrap_or_default(); + assert_eq!(encoded, "{\"bytes\":[104,105]}"); +} diff --git a/request/tests/body/mod.rs b/request/tests/body/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/request/tests/body/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/request/tests/builder/fn.rs b/request/tests/builder/fn.rs new file mode 100644 index 00000000..1cdc3803 --- /dev/null +++ b/request/tests/builder/fn.rs @@ -0,0 +1,272 @@ +use super::*; + +#[test] +fn test_builder_new_builds_empty_request() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.build(); + assert_eq!(request.get_url_ref(), ""); + assert!(request.get_method().is_unknown()); + assert!(request.get_headers_ref().is_empty()); + assert!(request.get_body_ref().get_bytes_ref().is_empty()); +} + +#[test] +fn test_builder_get_sets_method_and_url() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.get("http://get.test/").build(); + assert!(request.get_method().is_get()); + assert_eq!(request.get_url_ref(), "http://get.test/"); +} + +#[test] +fn test_builder_post_sets_method_and_url() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.post("http://post.test/").build(); + assert!(request.get_method().is_post()); + assert_eq!(request.get_url_ref(), "http://post.test/"); +} + +#[test] +fn test_builder_method_and_url_overrides_shortcuts() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder + .get("http://first.test/") + .method(Method::Put) + .url("http://second.test/") + .build(); + assert!(request.get_method() == Method::Put); + assert_eq!(request.get_url_ref(), "http://second.test/"); +} + +#[test] +fn test_builder_header_normalizes_key() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder + .get("http://a.test/") + .header("Accept", "application/json") + .build(); + assert_eq!( + request.get_headers_ref().get("accept").map(String::as_str), + Some("application/json") + ); +} + +#[test] +fn test_builder_headers_inserts_every_pair() { + let mut headers: HashMap = HashMap::new(); + headers.insert("A".to_string(), "1".to_string()); + headers.insert("B".to_string(), "2".to_string()); + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.get("http://a.test/").headers(headers).build(); + assert_eq!(request.get_headers_ref().len(), 2usize); + assert_eq!( + request.get_headers_ref().get("a").map(String::as_str), + Some("1") + ); + assert_eq!( + request.get_headers_ref().get("b").map(String::as_str), + Some("2") + ); +} + +#[test] +fn test_builder_remove_and_clear_headers() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder + .get("http://a.test/") + .header("A", "1") + .header("B", "2") + .remove_header("a") + .build(); + assert_eq!(request.get_headers_ref().len(), 1usize); + assert!(request.get_headers_ref().get("a").is_none()); + let mut second: RequestBuilder = RequestBuilder::new(); + let cleared: HttpRequest = second + .get("http://a.test/") + .header("A", "1") + .clear_headers() + .build(); + assert!(cleared.get_headers_ref().is_empty()); +} + +#[test] +fn test_builder_body_accepts_raw_bytes() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.post("http://a.test/").body(vec![1u8, 2, 3]).build(); + assert_eq!(request.get_body_ref().get_bytes_ref(), &[1u8, 2, 3]); +} + +#[test] +fn test_builder_body_text_stores_utf8_bytes() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.post("http://a.test/").body_text("héllo").build(); + assert_eq!(request.get_body_ref().as_str(), Some("héllo")); + assert_eq!(request.get_body_ref().get_bytes_ref().len(), 6usize); +} + +#[test] +fn test_builder_body_json_serialises_value() { + let body: serde_json::Value = json!({ "code": 1 }); + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.post("http://a.test/").body_json(&body).build(); + assert_eq!(request.get_body_ref().as_str(), Some("{\"code\":1}")); +} + +#[test] +fn test_builder_body_json_replaces_previous_body() { + let first: serde_json::Value = json!({ "a": 1 }); + let second: serde_json::Value = json!({ "b": 2 }); + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder + .post("http://a.test/") + .body_json(&first) + .body_json(&second) + .build(); + assert_eq!(request.get_body_ref().as_str(), Some("{\"b\":2}")); +} + +#[test] +fn test_builder_timeout_and_buffer_size() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder + .get("http://a.test/") + .timeout(6000u64) + .buffer_size(8192usize) + .build(); + assert_eq!(request.get_config_ref().get_timeout(), 6000u64); + assert_eq!(request.get_config_ref().get_buffer_size(), 8192usize); +} + +#[test] +fn test_builder_http_version_toggles() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let http2: HttpRequest = builder.get("http://a.test/").http2_only().build(); + assert_eq!( + *http2.get_config_ref().get_http_version(), + HttpVersion::Http2 + ); + let mut other: RequestBuilder = RequestBuilder::new(); + let http1: HttpRequest = other + .get("http://a.test/") + .http2_only() + .http1_1_only() + .build(); + assert_eq!( + *http1.get_config_ref().get_http_version(), + HttpVersion::Http1_1 + ); +} + +#[test] +fn test_builder_redirect_toggles() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.get("http://a.test/").build(); + assert!(!request.get_config_ref().get_redirect()); + let mut enabled: RequestBuilder = RequestBuilder::new(); + let redirected: HttpRequest = enabled.get("http://a.test/").redirect().build(); + assert!(redirected.get_config_ref().get_redirect()); + let mut disabled: RequestBuilder = RequestBuilder::new(); + let plain: HttpRequest = disabled + .get("http://a.test/") + .redirect() + .no_redirect() + .build(); + assert!(!plain.get_config_ref().get_redirect()); +} + +#[test] +fn test_builder_max_redirect_times() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder + .get("http://a.test/") + .max_redirect_times(5usize) + .build(); + assert_eq!(request.get_config_ref().get_max_redirect_times(), 5usize); +} + +#[test] +fn test_builder_decode_toggles() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder.get("http://a.test/").build(); + assert!(!request.get_config_ref().get_decode()); + let mut enabled: RequestBuilder = RequestBuilder::new(); + let decoded: HttpRequest = enabled.get("http://a.test/").decode().build(); + assert!(decoded.get_config_ref().get_decode()); + let mut disabled: RequestBuilder = RequestBuilder::new(); + let plain: HttpRequest = disabled.get("http://a.test/").decode().no_decode().build(); + assert!(!plain.get_config_ref().get_decode()); +} + +#[test] +fn test_builder_proxy_and_no_proxy() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let request: HttpRequest = builder + .get("http://a.test/") + .proxy(Proxy::socks5("127.0.0.1", 1080u16)) + .build(); + let proxy: &Option = request.get_config_ref().get_proxy(); + assert_eq!( + proxy.as_ref().map(|item: &Proxy| item.proxy_type), + Some(ProxyType::Socks5) + ); + assert_eq!(proxy.as_ref().map(|item: &Proxy| item.port), Some(1080u16)); + let mut cleared: RequestBuilder = RequestBuilder::new(); + let direct: HttpRequest = cleared + .get("http://a.test/") + .proxy(Proxy::http("h", 1u16)) + .no_proxy() + .build(); + assert!(direct.get_config_ref().get_proxy().is_none()); +} + +#[test] +fn test_builder_build_resets_the_builder() { + let mut builder: RequestBuilder = RequestBuilder::new(); + builder.get("http://a.test/"); + let first: HttpRequest = builder.build(); + let second: HttpRequest = builder.build(); + assert_eq!(first.get_url_ref(), "http://a.test/"); + assert_eq!(second.get_url_ref(), ""); + assert!(second.get_headers_ref().is_empty()); +} + +#[test] +fn test_builder_get_request_mut_exposes_underlying_request() { + let mut builder: RequestBuilder = RequestBuilder::new(); + let inner: &mut HttpRequest = builder.get_request_mut(); + inner.set_url("http://inner.test/"); + inner.set_header("X-Token", "v"); + let request: HttpRequest = builder.build(); + assert_eq!(request.get_url_ref(), "http://inner.test/"); + assert_eq!( + request.get_headers_ref().get("x-token").map(String::as_str), + Some("v") + ); +} + +#[test] +fn test_builder_default_and_clone_preserve_state() { + let mut base: RequestBuilder = RequestBuilder::default(); + base.get("http://base.test/").header("A", "1"); + let mut cloned: RequestBuilder = base.clone(); + let from_clone: HttpRequest = cloned.build(); + let from_base: HttpRequest = base.build(); + assert_eq!(from_clone.get_url_ref(), "http://base.test/"); + assert_eq!(from_base.get_url_ref(), "http://base.test/"); + assert_eq!( + from_clone.get_headers_ref().get("a").map(String::as_str), + Some("1") + ); + assert_eq!( + from_base.get_headers_ref().get("a").map(String::as_str), + Some("1") + ); +} + +#[test] +fn test_builder_debug_lists_url() { + let mut builder: RequestBuilder = RequestBuilder::new(); + builder.get("http://debug.test/"); + let debugged: String = format!("{builder:?}"); + assert!(debugged.contains("http://debug.test/")); +} diff --git a/request/tests/builder/mod.rs b/request/tests/builder/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/request/tests/builder/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/request/tests/mod.rs b/request/tests/mod.rs index 3df04af8..d8f90818 100644 --- a/request/tests/mod.rs +++ b/request/tests/mod.rs @@ -1,4 +1,10 @@ +mod body; +mod builder; +mod proxy; +mod request_api; mod request_builder; +mod response; +mod url; use http_request::*; diff --git a/request/tests/proxy/fn.rs b/request/tests/proxy/fn.rs new file mode 100644 index 00000000..c6922b30 --- /dev/null +++ b/request/tests/proxy/fn.rs @@ -0,0 +1,90 @@ +use super::*; + +#[test] +fn test_proxy_http_sets_http_variant() { + let proxy: Proxy = Proxy::http("127.0.0.1", 7890u16); + assert_eq!(proxy.proxy_type, ProxyType::Http); + assert_eq!(proxy.host, "127.0.0.1"); + assert_eq!(proxy.port, 7890u16); + assert!(proxy.username.is_none()); + assert!(proxy.password.is_none()); +} + +#[test] +fn test_proxy_https_sets_https_variant() { + let proxy: Proxy = Proxy::https("proxy.example.com", 443u16); + assert_eq!(proxy.proxy_type, ProxyType::Https); + assert_eq!(proxy.host, "proxy.example.com"); + assert_eq!(proxy.port, 443u16); + assert!(proxy.username.is_none()); + assert!(proxy.password.is_none()); +} + +#[test] +fn test_proxy_socks5_sets_socks5_variant() { + let proxy: Proxy = Proxy::socks5("127.0.0.1", 1080u16); + assert_eq!(proxy.proxy_type, ProxyType::Socks5); + assert_eq!(proxy.host, "127.0.0.1"); + assert_eq!(proxy.port, 1080u16); + assert!(proxy.username.is_none()); + assert!(proxy.password.is_none()); +} + +#[test] +fn test_proxy_accepts_owned_string_host() { + let proxy: Proxy = Proxy::http(String::from("owned.host"), 1u16); + assert_eq!(proxy.host, "owned.host"); +} + +#[test] +fn test_proxy_auth_sets_username_and_password() { + let proxy: Proxy = Proxy::socks5("127.0.0.1", 1080u16).auth("user", "pass"); + assert_eq!(proxy.username, Some("user".to_string())); + assert_eq!(proxy.password, Some("pass".to_string())); + assert_eq!(proxy.proxy_type, ProxyType::Socks5); +} + +#[test] +fn test_proxy_auth_overwrites_previous_credentials() { + let proxy: Proxy = Proxy::http("h", 1u16) + .auth("first", "one") + .auth("second", "two"); + assert_eq!(proxy.username, Some("second".to_string())); + assert_eq!(proxy.password, Some("two".to_string())); +} + +#[test] +fn test_proxy_type_is_copy_and_eq() { + let proxy: Proxy = Proxy::https("h", 2u16); + let copied: ProxyType = proxy.proxy_type; + assert_eq!(copied, ProxyType::Https); + assert_eq!(proxy.proxy_type, copied); +} + +#[test] +fn test_proxy_type_variants_are_distinct() { + assert_ne!(ProxyType::Http, ProxyType::Https); + assert_ne!(ProxyType::Https, ProxyType::Socks5); + assert_ne!(ProxyType::Http, ProxyType::Socks5); +} + +#[test] +fn test_proxy_equality_compares_every_field() { + assert_eq!(Proxy::http("h", 1u16), Proxy::http("h", 1u16)); + assert_ne!(Proxy::http("h", 1u16), Proxy::https("h", 1u16)); + assert_ne!(Proxy::http("h", 1u16), Proxy::http("h2", 1u16)); + assert_ne!(Proxy::http("h", 1u16), Proxy::http("h", 2u16)); + assert_ne!( + Proxy::http("h", 1u16), + Proxy::http("h", 1u16).auth("u", "p") + ); +} + +#[test] +fn test_proxy_clone_preserves_credentials() { + let proxy: Proxy = Proxy::socks5("h", 5u16).auth("u", "p"); + let cloned: Proxy = proxy.clone(); + assert_eq!(cloned, proxy); + assert_eq!(cloned.username, Some("u".to_string())); + assert_eq!(cloned.password, Some("p".to_string())); +} diff --git a/request/tests/proxy/mod.rs b/request/tests/proxy/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/request/tests/proxy/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/request/tests/request_api/fn.rs b/request/tests/request_api/fn.rs new file mode 100644 index 00000000..dfaaedca --- /dev/null +++ b/request/tests/request_api/fn.rs @@ -0,0 +1,230 @@ +use super::*; + +#[test] +fn test_http_request_get_sets_method_and_url() { + let request: HttpRequest = HttpRequest::get("http://example.com/a"); + assert_eq!(request.get_method(), Method::Get); + assert!(request.get_method().is_get()); + assert_eq!(request.get_url(), "http://example.com/a"); + assert_eq!(request.get_url_ref(), "http://example.com/a"); + assert!(request.get_headers().is_empty()); + assert!(request.get_body_ref().get_bytes_ref().is_empty()); +} + +#[test] +fn test_http_request_post_sets_method_and_url() { + let request: HttpRequest = HttpRequest::post("https://example.com/b"); + assert_eq!(request.get_method(), Method::Post); + assert!(request.get_method().is_post()); + assert_eq!(request.get_url_ref(), "https://example.com/b"); +} + +#[test] +fn test_http_request_accepts_owned_url() { + let request: HttpRequest = HttpRequest::get(String::from("http://owned.test/")); + assert_eq!(request.get_url_ref(), "http://owned.test/"); +} + +#[test] +fn test_http_request_default_is_empty_request() { + let request: HttpRequest = HttpRequest::default(); + assert_eq!(request.get_url_ref(), ""); + assert_eq!(request.url, ""); + assert!(request.get_method().is_unknown()); + assert!(request.get_headers_ref().is_empty()); + assert!(request.get_body_ref().get_bytes_ref().is_empty()); + assert_eq!( + *request.get_config_ref().get_http_version(), + HttpVersion::Http1_1 + ); +} + +#[test] +fn test_http_request_get_sets_url_matches_field() { + let request: HttpRequest = HttpRequest::get("http://example.com/"); + assert_eq!(request.url, "http://example.com/"); + assert_eq!(request.method, Method::Get); +} + +#[test] +fn test_http_request_set_method_overrides_default() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + request.set_method(Method::Put); + assert_eq!(request.get_method(), Method::Put); + assert!(!request.get_method().is_get()); + assert_eq!(request.method, Method::Put); +} + +#[test] +fn test_http_request_set_url_overrides_previous() { + let mut request: HttpRequest = HttpRequest::get("http://first.test/"); + request.set_url("http://second.test/"); + assert_eq!(request.get_url_ref(), "http://second.test/"); +} + +#[test] +fn test_http_request_set_header_normalizes_key_to_lowercase() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + request.set_header("Content-Type", "application/json"); + let headers: &HashMap = request.get_headers_ref(); + assert_eq!(headers.len(), 1usize); + assert_eq!( + headers.get("content-type").map(String::as_str), + Some("application/json") + ); + assert!(headers.get("Content-Type").is_none()); +} + +#[test] +fn test_http_request_set_header_last_write_wins_across_cases() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + request.set_header("X-Token", "first"); + request.set_header("x-token", "second"); + let headers: &HashMap = request.get_headers_ref(); + assert_eq!(headers.len(), 1usize); + assert_eq!(headers.get("x-token").map(String::as_str), Some("second")); + assert!(headers.get("X-Token").is_none()); +} + +#[test] +fn test_http_request_remove_header_is_case_insensitive() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + request.set_header("X-Token", "value"); + request.remove_header("x-token"); + assert!(request.get_headers_ref().is_empty()); + request.set_header("X-Token", "value"); + request.remove_header("X-TOKEN"); + assert!(request.get_headers_ref().is_empty()); +} + +#[test] +fn test_http_request_remove_missing_header_is_noop() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + request.set_header("X-Token", "value"); + request.remove_header("absent"); + assert_eq!(request.get_headers_ref().len(), 1usize); +} + +#[test] +fn test_http_request_clear_headers_drops_all() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + request.set_header("A", "1"); + request.set_header("B", "2"); + assert_eq!(request.get_headers().len(), 2usize); + request.clear_headers(); + assert!(request.get_headers_ref().is_empty()); +} + +#[test] +fn test_http_request_get_mut_headers_writes_directly() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + let headers: &mut HashMap = request.get_mut_headers(); + headers.insert("X-raw".to_string(), "v".to_string()); + assert_eq!( + request.get_headers_ref().get("X-raw").map(String::as_str), + Some("v") + ); +} + +#[test] +fn test_http_request_set_body_replaces_previous() { + let mut request: HttpRequest = HttpRequest::post("http://example.com/"); + request.set_body(Body::from_bytes("data")); + assert_eq!(request.get_body().get_bytes_ref(), b"data"); + assert_eq!(request.get_body_ref().as_str(), Some("data")); + request.set_body(Body::empty()); + assert!(request.get_body_ref().get_bytes_ref().is_empty()); + assert_eq!(request.body, Body::empty()); +} + +#[test] +fn test_http_request_config_defaults() { + let request: HttpRequest = HttpRequest::get("http://example.com/"); + assert_eq!(request.get_config_ref().get_buffer_size(), 0usize); + assert_eq!(request.get_config_ref().get_timeout(), 0u64); + assert_eq!(request.get_config_ref().get_max_redirect_times(), 0usize); + assert_eq!( + *request.get_config_ref().get_http_version(), + HttpVersion::Http1_1 + ); + assert!(!request.get_config_ref().get_redirect()); + assert!(!request.get_config_ref().get_decode()); + assert!(request.get_config_ref().get_proxy().is_none()); +} + +#[test] +fn test_http_request_config_setters_are_chainable() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + request + .get_config_mut() + .set_buffer_size(4096usize) + .set_timeout(1500u64) + .set_max_redirect_times(3usize) + .set_http_version(HttpVersion::Http2) + .set_redirect(true) + .set_decode(true); + assert_eq!(request.get_config_ref().get_buffer_size(), 4096usize); + assert_eq!(request.get_config_ref().get_timeout(), 1500u64); + assert_eq!(request.get_config_ref().get_max_redirect_times(), 3usize); + assert_eq!( + *request.get_config_ref().get_http_version(), + HttpVersion::Http2 + ); + assert!(request.get_config_ref().get_redirect()); + assert!(request.get_config_ref().get_decode()); +} + +#[test] +fn test_http_request_config_proxy_setter() { + let mut request: HttpRequest = HttpRequest::get("http://example.com/"); + request + .get_config_mut() + .set_proxy(Some(Proxy::http("h", 1u16))); + let proxy: &Option = request.get_config_ref().get_proxy(); + assert_eq!( + proxy.as_ref().map(|item: &Proxy| item.host.clone()), + Some("h".to_string()) + ); + request.get_config_mut().set_proxy(None); + assert!(request.get_config_ref().get_proxy().is_none()); +} + +#[test] +fn test_http_request_set_config_replaces_whole_config() { + let mut source: HttpRequest = HttpRequest::get("http://source.test/"); + source.get_config_mut().set_timeout(1234u64); + source.get_config_mut().set_buffer_size(64usize); + let mut target: HttpRequest = HttpRequest::post("http://target.test/"); + target.get_config_mut().set_timeout(9999u64); + target.set_config(source.get_config()); + assert_eq!(target.get_config(), source.get_config()); + assert_eq!(target.get_config_ref().get_timeout(), 1234u64); + assert_eq!(target.get_config_ref().get_buffer_size(), 64usize); + assert_eq!(target.config, source.config); +} + +#[test] +fn test_http_request_get_config_returns_owned_copy() { + let request: HttpRequest = HttpRequest::get("http://example.com/"); + assert_eq!(request.get_config(), request.get_config()); + assert_eq!(request.config, request.get_config()); +} + +#[test] +fn test_http_request_clone_copies_headers_and_body() { + let mut request: HttpRequest = HttpRequest::post("http://example.com/"); + request.set_header("X-Token", "v"); + request.set_body(Body::from_bytes("payload")); + let cloned: HttpRequest = request.clone(); + assert_eq!(cloned.get_url_ref(), request.get_url_ref()); + assert_eq!(cloned.get_method(), request.get_method()); + assert_eq!(cloned.get_headers_ref(), request.get_headers_ref()); + assert_eq!(cloned.get_body_ref(), request.get_body_ref()); +} + +#[test] +fn test_http_request_debug_lists_url() { + let request: HttpRequest = HttpRequest::get("http://debug.test/"); + let debugged: String = format!("{request:?}"); + assert!(debugged.contains("http://debug.test/")); +} diff --git a/request/tests/request_api/mod.rs b/request/tests/request_api/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/request/tests/request_api/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/request/tests/response/fn.rs b/request/tests/response/fn.rs new file mode 100644 index 00000000..9ec13985 --- /dev/null +++ b/request/tests/response/fn.rs @@ -0,0 +1,198 @@ +use super::*; + +#[test] +fn test_response_default_is_unknown() { + let response: HttpResponse = HttpResponse::default(); + assert_eq!(response.status_code, HttpStatus::Unknown.code()); + assert_eq!(response.status_code, 0usize); + assert_eq!(response.reason_phrase, "Unknown"); + assert_eq!(response.version, HttpVersion::Http1_1); + assert!(response.headers.is_empty()); + assert!(response.body.is_empty()); + assert!(!response.is_success()); + assert!(!response.is_redirect()); +} + +#[test] +fn test_response_from_bytes_parses_status_headers_body() { + let response: HttpResponse = HttpResponse::from_bytes( + b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nX-Empty:\r\n\r\nhello", + ); + assert_eq!(response.status_code, 200usize); + assert_eq!(response.reason_phrase, "OK"); + assert_eq!(response.version, HttpVersion::Http1_1); + assert_eq!(response.headers.len(), 1usize); + assert_eq!( + response.get_header("content-type"), + Some("application/json") + ); + assert_eq!(response.bytes(), b"hello"); + assert_eq!(response.text(), "hello"); + assert!(response.is_success()); + assert!(!response.is_redirect()); +} + +#[test] +fn test_response_get_header_is_case_insensitive() { + let response: HttpResponse = HttpResponse::from_bytes( + b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nX-Empty:\r\n\r\nhello", + ); + assert_eq!( + response.get_header("Content-Type"), + Some("application/json") + ); + assert_eq!( + response.get_header("CONTENT-TYPE"), + Some("application/json") + ); + assert_eq!(response.get_header("absent"), None); +} + +#[test] +fn test_response_from_bytes_skips_valueless_header() { + let response: HttpResponse = HttpResponse::from_bytes( + b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nX-Empty:\r\n\r\nhello", + ); + assert!(response.get_header("x-empty").is_none()); + assert!(!response.headers.contains_key("x-empty")); +} + +#[test] +fn test_response_from_bytes_parses_redirect() { + let raw: &[u8] = b"HTTP/1.1 302 Found\r\nLocation: http://target.test/\r\n\r\n"; + let response: HttpResponse = HttpResponse::from_bytes(raw); + assert_eq!(response.status_code, 302usize); + assert_eq!(response.reason_phrase, "Found"); + assert_eq!(response.get_header("location"), Some("http://target.test/")); + assert!(response.is_redirect()); + assert!(!response.is_success()); +} + +#[test] +fn test_response_from_bytes_joins_multi_word_reason_and_body() { + let raw: &[u8] = b"HTTP/1.1 500 Internal Server Error\r\nX-Trace: t1\r\n\r\nline1\r\nline2"; + let response: HttpResponse = HttpResponse::from_bytes(raw); + assert_eq!(response.status_code, 500usize); + assert_eq!(response.reason_phrase, "Internal Server Error"); + assert_eq!(response.get_header("x-trace"), Some("t1")); + assert_eq!(response.bytes(), b"line1\nline2"); + assert!(!response.is_success()); + assert!(!response.is_redirect()); +} + +#[test] +fn test_response_from_bytes_falls_back_on_unparsable_status_line() { + let raw: &[u8] = b"garbage\r\n\r\n"; + let response: HttpResponse = HttpResponse::from_bytes(raw); + assert_eq!(response.status_code, HttpStatus::Unknown.code()); + assert_eq!(response.reason_phrase, "Unknown"); + assert!(response.version.is_unknown()); + assert!(response.body.is_empty()); +} + +#[test] +fn test_response_from_bytes_handles_empty_input() { + let response: HttpResponse = HttpResponse::from_bytes(&[]); + assert_eq!(response.status_code, HttpStatus::Unknown.code()); + assert_eq!(response.reason_phrase, "Unknown"); + assert!(response.body.is_empty()); + assert!(response.headers.is_empty()); +} + +#[test] +fn test_response_from_bytes_status_code_needs_three_digits() { + let raw: &[u8] = b"HTTP/1.1 20 OK\r\n\r\n"; + let response: HttpResponse = HttpResponse::from_bytes(raw); + assert_eq!(response.status_code, 20usize); +} + +#[test] +fn test_response_getters_read_public_fields() { + let response: HttpResponse = HttpResponse::from_bytes( + b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nX-Empty:\r\n\r\nhello", + ); + assert_eq!(response.get_status_code(), response.status_code); + assert_eq!(response.get_reason_phrase(), &response.reason_phrase); + assert_eq!(response.get_headers(), &response.headers); + assert_eq!(response.get_body(), &response.body); + assert_eq!(response.get_version(), &response.version); +} + +#[test] +fn test_response_setters_write_public_fields() { + let mut response: HttpResponse = HttpResponse::default(); + response.set_status_code(201usize); + response.set_reason_phrase("Created"); + response.set_version(HttpVersion::Http2); + response.set_body(vec![b'a', b'b', b'c']); + assert_eq!(response.status_code, 201usize); + assert_eq!(response.reason_phrase, "Created"); + assert_eq!(response.version, HttpVersion::Http2); + assert_eq!(response.bytes(), b"abc"); +} + +#[test] +fn test_response_set_headers_replaces_map() { + let mut response: HttpResponse = HttpResponse::from_bytes( + b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nX-Empty:\r\n\r\nhello", + ); + let mut replacement: HttpResponseHeaders = new_response_headers(); + replacement.insert("x-only".to_string(), "1".to_string()); + response.set_headers(replacement); + assert_eq!(response.headers.len(), 1usize); + assert_eq!(response.get_header("x-only"), Some("1")); + assert!(response.get_header("content-type").is_none()); +} + +#[test] +fn test_response_clone_is_equal_by_content() { + let response: HttpResponse = HttpResponse::from_bytes( + b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nX-Empty:\r\n\r\nhello", + ); + let cloned: HttpResponse = response.clone(); + assert_eq!(cloned.status_code, response.status_code); + assert_eq!(cloned.reason_phrase, response.reason_phrase); + assert_eq!(cloned.headers, response.headers); + assert_eq!(cloned.body, response.body); +} + +#[test] +fn test_response_decode_without_content_encoding_keeps_body() { + let response: HttpResponse = HttpResponse::from_bytes( + b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nX-Empty:\r\n\r\nhello", + ); + let decoded: HttpResponse = response.decode(4096usize); + assert_eq!(decoded.body, response.body); + assert_eq!(decoded.status_code, response.status_code); + assert_eq!(decoded.reason_phrase, response.reason_phrase); + assert_eq!(decoded.headers, response.headers); + assert_eq!(decoded.version, response.version); +} + +#[test] +fn test_response_text_replaces_invalid_utf8() { + let mut response: HttpResponse = HttpResponse::default(); + response.set_body(vec![b'a', 0xff, b'b']); + assert_eq!(response.bytes(), &[b'a', 0xff, b'b']); + assert_eq!(response.text(), "a\u{fffd}b"); + assert_eq!(response.text().len(), 5usize); +} + +#[test] +fn test_new_response_headers_starts_empty() { + let mut headers: HttpResponseHeaders = new_response_headers(); + assert!(headers.is_empty()); + headers.insert("k".to_string(), "v".to_string()); + assert_eq!(headers.len(), 1usize); + assert_eq!(headers.get("k").map(String::as_str), Some("v")); +} + +#[test] +fn test_response_debug_lists_status() { + let response: HttpResponse = HttpResponse::from_bytes( + b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nX-Empty:\r\n\r\nhello", + ); + let debugged: String = format!("{response:?}"); + assert!(debugged.contains("200")); + assert!(debugged.contains("OK")); +} diff --git a/request/tests/response/mod.rs b/request/tests/response/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/request/tests/response/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/request/tests/url/fn.rs b/request/tests/url/fn.rs new file mode 100644 index 00000000..0e9285fb --- /dev/null +++ b/request/tests/url/fn.rs @@ -0,0 +1,129 @@ +use super::*; + +#[test] +fn test_url_components_parse_full_url() { + let components: HttpUrlComponents = + HttpUrlComponents::parse("https://host.test:8443/a/b?x=1&y=2#frag").unwrap_or_default(); + assert_eq!(components.protocol, "https"); + assert_eq!(components.host, Some("host.test".to_string())); + assert_eq!(components.port, Some(8443u16)); + assert_eq!(components.path, Some("/a/b".to_string())); + assert_eq!(components.query, Some("x=1&y=2".to_string())); + assert_eq!(components.fragment, Some("frag".to_string())); +} + +#[test] +fn test_url_components_parse_bare_host_uses_root_path() { + let components: HttpUrlComponents = + HttpUrlComponents::parse("http://host.test").unwrap_or_default(); + assert_eq!(components.protocol, "http"); + assert_eq!(components.host, Some("host.test".to_string())); + assert_eq!(components.port, None); + assert_eq!(components.path, Some("/".to_string())); + assert_eq!(components.query, None); + assert_eq!(components.fragment, None); +} + +#[test] +fn test_url_components_parse_keeps_empty_query() { + let components: HttpUrlComponents = + HttpUrlComponents::parse("http://host.test/p?").unwrap_or_default(); + assert_eq!(components.query, Some(String::new())); + assert_eq!(components.path, Some("/p".to_string())); +} + +#[test] +fn test_url_components_parse_rejects_relative_url() { + assert!(HttpUrlComponents::parse("not a url").is_err()); + assert!(HttpUrlComponents::parse("").is_err()); +} + +#[test] +fn test_url_components_default_is_all_empty() { + let components: HttpUrlComponents = HttpUrlComponents::default(); + assert_eq!(components.protocol, ""); + assert_eq!(components.host, None); + assert_eq!(components.port, None); + assert_eq!(components.path, None); + assert_eq!(components.query, None); + assert_eq!(components.fragment, None); +} + +#[test] +fn test_url_components_equality_and_clone() { + let first: HttpUrlComponents = HttpUrlComponents::parse("http://a.test/x").unwrap_or_default(); + let second: HttpUrlComponents = first.clone(); + assert_eq!(first, second); + let other: HttpUrlComponents = HttpUrlComponents::parse("http://a.test/y").unwrap_or_default(); + assert_ne!(first, other); +} + +#[test] +fn test_url_parse_error_display_message() { + let message: String = match HttpUrlComponents::parse("not a url") { + Ok(parsed) => format!("ok:{parsed:?}"), + Err(error) => format!("{error}"), + }; + assert_eq!(message, "Invalid URL"); +} + +#[test] +fn test_request_headers_alias_is_single_valued_map() { + let mut headers: RequestHeaders = hash_map_xx_hash3_64(); + assert!(headers.is_empty()); + headers.insert("accept".to_string(), "*/*".to_string()); + assert_eq!(headers.len(), 1usize); + assert_eq!(headers.get("accept").map(String::as_str), Some("*/*")); +} + +#[test] +fn test_request_result_alias_carries_request_error() { + let result: RequestResult = Err(RequestError::Request("boom".to_string())); + assert!(result.is_err()); + let ok: RequestResult = Ok(HttpResponse::default()); + assert!(ok.is_ok()); +} + +#[test] +fn test_request_error_display_matches_debug() { + let error: RequestError = RequestError::Request("boom".to_string()); + let displayed: String = format!("{error}"); + let debugged: String = format!("{error:?}"); + assert!(displayed.contains("Request")); + assert_eq!(displayed, debugged); +} + +#[test] +fn test_request_error_clone_and_equality() { + let error: RequestError = RequestError::Request("boom".to_string()); + let cloned: RequestError = error.clone(); + assert_eq!(cloned, error); + assert_ne!(cloned, RequestError::Request("other".to_string())); +} + +#[test] +fn test_app_name_constant() { + assert_eq!(APP_NAME, "http-request"); +} + +#[test] +fn test_header_name_constants() { + assert_eq!(ACCEPT, "accept"); + assert_eq!(HOST, "host"); + assert_eq!(LOCATION, "location"); + assert_eq!(CONTENT_LENGTH, "content-length"); + assert_eq!(CONTENT_TYPE, "content-type"); + assert_eq!(USER_AGENT, "user-agent"); + assert_eq!(ACCEPT_ANY, "*/*"); + assert_eq!(HTTPS_LOWERCASE, "https"); + assert_eq!(QUERY, "?"); +} + +#[test] +fn test_byte_constants() { + assert_eq!(SPACE_U8, b' '); + assert_eq!(TAB_U8, b'\t'); + assert_eq!(COLON_U8, b':'); + assert_eq!(BR_BYTES, b"\n"); + assert_eq!(HTTP_BR_BYTES, b"\r\n"); +} diff --git a/request/tests/url/mod.rs b/request/tests/url/mod.rs new file mode 100644 index 00000000..e41822a8 --- /dev/null +++ b/request/tests/url/mod.rs @@ -0,0 +1,3 @@ +mod r#fn; + +use super::*; diff --git a/type/src/content_type/const.rs b/type/src/content_type/const.rs new file mode 100644 index 00000000..b6cd6e21 --- /dev/null +++ b/type/src/content_type/const.rs @@ -0,0 +1,5 @@ +/// Opening markup of the single-cell table used by the HTML body renderer. +pub(crate) const HTML_TABLE_OPEN: &str = "
"; + +/// Closing markup of the single-cell table used by the HTML body renderer. +pub(crate) const HTML_TABLE_CLOSE: &str = "
"; diff --git a/type/src/content_type/impl.rs b/type/src/content_type/impl.rs index ec9a00e8..768afe0d 100644 --- a/type/src/content_type/impl.rs +++ b/type/src/content_type/impl.rs @@ -6,7 +6,7 @@ impl ContentType { /// /// # Arguments /// - /// - `&(Serialize + Display)` - Data to serialize + /// - `&T` - Data to serialize, which requires the `Serialize` and `Display` bounds /// /// # Returns /// @@ -22,7 +22,7 @@ impl ContentType { /// /// # Arguments /// - /// - `&(Serialize + Display)` - Data to serialize + /// - `&T` - Data to serialize, which requires the `Serialize` and `Display` bounds /// /// # Returns /// @@ -38,7 +38,8 @@ impl ContentType { /// /// # Arguments /// - /// - `&(Serialize + Debug + Clone + Default + Display)` - Data to format + /// - `&T` - Data to format, which requires the `Serialize`, `Debug`, `Clone`, `Default` and + /// `Display` bounds /// /// # Returns /// @@ -54,7 +55,8 @@ impl ContentType { /// /// # Arguments /// - /// - `&(Serialize + Debug + Clone + Default)` - Data to format + /// - `&T` - Data to format, which requires the `Serialize`, `Debug`, `Clone` and `Default` + /// bounds /// /// # Returns /// @@ -64,9 +66,9 @@ impl ContentType { T: Serialize + Debug + Clone + Default, { let mut html: String = String::with_capacity(64); - html.push_str("
"); + html.push_str(HTML_TABLE_OPEN); html.push_str(&format!("{data:?}")); - html.push_str("
"); + html.push_str(HTML_TABLE_CLOSE); html } @@ -74,7 +76,7 @@ impl ContentType { /// /// # Arguments /// - /// - `&(Serialize + Display)` - Data to serialize + /// - `&T` - Data to serialize, which requires the `Serialize` and `Display` bounds /// /// # Returns /// @@ -90,7 +92,8 @@ impl ContentType { /// /// # Arguments /// - /// - `&(Serialize + Debug + Clone + Default + Display)` - Data to format + /// - `&T` - Data to format, which requires the `Serialize`, `Debug`, `Clone`, `Default` and + /// `Display` bounds /// /// # Returns /// @@ -106,7 +109,8 @@ impl ContentType { /// /// # Arguments /// - /// - `&(Serialize + Debug + Clone + Default + Display)` - Data to format + /// - `&T` - Data to format, which requires the `Serialize`, `Debug`, `Clone`, `Default` and + /// `Display` bounds /// /// # Returns /// @@ -129,8 +133,8 @@ impl ContentType { /// /// # Arguments /// - /// - `AsRef` - Content type - /// - `AsRef` - Charset + /// - `T` - Content type, which requires the `AsRef` bound + /// - `S` - Charset, which requires the `AsRef` bound /// /// # Returns /// @@ -159,8 +163,8 @@ impl ContentType { /// /// # Arguments /// - /// - `AsRef` - Content type - /// - `AsRef` - Charset declaration + /// - `T` - Content type, which requires the `AsRef` bound + /// - `S` - Charset declaration, which requires the `AsRef` bound /// /// # Returns /// diff --git a/type/src/content_type/mod.rs b/type/src/content_type/mod.rs index d372be7d..b4837b98 100644 --- a/type/src/content_type/mod.rs +++ b/type/src/content_type/mod.rs @@ -1,6 +1,9 @@ +mod r#const; mod r#enum; mod r#impl; pub use r#enum::*; +pub(crate) use r#const::*; + use super::*; diff --git a/type/src/cookie/impl.rs b/type/src/cookie/impl.rs index 2d0adfc1..f49f05de 100644 --- a/type/src/cookie/impl.rs +++ b/type/src/cookie/impl.rs @@ -6,8 +6,8 @@ impl CookieBuilder { /// /// # Arguments /// - /// - `AsRef` - The cookie name type. - /// - `AsRef` - The cookie value type. + /// - `N` - The cookie name, which requires the `AsRef` bound. + /// - `V` - The cookie value, which requires the `AsRef` bound. /// /// # Returns /// @@ -38,7 +38,7 @@ impl CookieBuilder { /// /// # Arguments /// - /// - `AsRef` - The `Set-Cookie` header string to parse. + /// - `C` - The `Set-Cookie` header string to parse, which requires the `AsRef` bound. /// /// # Returns /// @@ -110,7 +110,7 @@ impl CookieBuilder { /// /// # Arguments /// - /// - `AsRef` - The expiration date string. + /// - `E` - The expiration date, which requires the `AsRef` bound. /// /// # Returns /// @@ -128,7 +128,7 @@ impl CookieBuilder { /// /// # Arguments /// - /// - `Into` - The maximum age in seconds. + /// - `M` - The maximum age in seconds, which requires the `Into` bound. /// /// # Returns /// @@ -146,7 +146,7 @@ impl CookieBuilder { /// /// # Arguments /// - /// - `AsRef` - The domain for the cookie. + /// - `D` - The domain for the cookie, which requires the `AsRef` bound. /// /// # Returns /// @@ -164,7 +164,7 @@ impl CookieBuilder { /// /// # Arguments /// - /// - `AsRef` - The path for the cookie. + /// - `T` - The path for the cookie, which requires the `AsRef` bound. /// /// # Returns /// @@ -236,7 +236,7 @@ impl CookieBuilder { /// /// # Arguments /// - /// - `AsRef` - The `SameSite` policy. + /// - `T` - The `SameSite` policy, which requires the `AsRef` bound. /// /// # Returns /// @@ -299,7 +299,7 @@ impl Cookie { /// /// # Arguments /// - /// - `AsRef` - The `Cookie` header string to parse. + /// - `C` - The `Cookie` header string to parse, which requires the `AsRef` bound. /// /// # Returns /// diff --git a/type/src/cookie/struct.rs b/type/src/cookie/struct.rs index bf99320b..556f9975 100644 --- a/type/src/cookie/struct.rs +++ b/type/src/cookie/struct.rs @@ -16,6 +16,7 @@ pub struct CookieBuilder { #[set(skip)] pub(super) expires: Option, /// Optional maximum age in seconds. + #[get(type(copy))] #[set(skip)] pub(super) max_age: Option, /// Optional domain scope for the cookie. @@ -25,9 +26,11 @@ pub struct CookieBuilder { #[set(skip)] pub(super) path: Option, /// Optional flag indicating secure (HTTPS-only) transmission. + #[get(type(copy))] #[set(skip)] pub(super) secure: Option, /// Optional flag preventing JavaScript access. + #[get(type(copy))] #[set(skip)] pub(super) http_only: Option, /// Optional SameSite policy setting. diff --git a/type/src/file_extension/const.rs b/type/src/file_extension/const.rs new file mode 100644 index 00000000..d21a9f0b --- /dev/null +++ b/type/src/file_extension/const.rs @@ -0,0 +1,2 @@ +/// The file extension for WebAssembly binaries. +pub(crate) const FILE_EXTENSION_WASM: &str = "wasm"; diff --git a/type/src/file_extension/impl.rs b/type/src/file_extension/impl.rs index e09b7ac9..d9803b64 100644 --- a/type/src/file_extension/impl.rs +++ b/type/src/file_extension/impl.rs @@ -5,7 +5,7 @@ impl FileExtension { /// /// # Arguments /// - /// - `AsRef` - The string to parse. + /// - `F` - The string to parse, which requires the `AsRef` bound. /// /// # Returns /// @@ -22,7 +22,7 @@ impl FileExtension { /// /// # Arguments /// - /// - `AsRef` - The full path to the file. + /// - `F` - The full path to the file, which requires the `AsRef` bound. /// /// # Returns /// @@ -2100,7 +2100,7 @@ impl FromStr for FileExtension { FILE_EXTENSION_WEBA => Ok(Self::FileExtensionWeba), FILE_EXTENSION_WEBM => Ok(Self::FileExtensionWebm), FILE_EXTENSION_WEBP => Ok(Self::FileExtensionWebp), - "wasm" => Ok(Self::FileExtensionWasm), + FILE_EXTENSION_WASM => Ok(Self::FileExtensionWasm), FILE_EXTENSION_WG => Ok(Self::FileExtensionWg), FILE_EXTENSION_WGT => Ok(Self::FileExtensionWgt), FILE_EXTENSION_WKS => Ok(Self::FileExtensionWks), @@ -3179,7 +3179,7 @@ impl Display for FileExtension { Self::FileExtensionWeba => FILE_EXTENSION_WEBA, Self::FileExtensionWebm => FILE_EXTENSION_WEBM, Self::FileExtensionWebp => FILE_EXTENSION_WEBP, - Self::FileExtensionWasm => "wasm", + Self::FileExtensionWasm => FILE_EXTENSION_WASM, Self::FileExtensionWg => FILE_EXTENSION_WG, Self::FileExtensionWgt => FILE_EXTENSION_WGT, Self::FileExtensionWks => FILE_EXTENSION_WKS, diff --git a/type/src/file_extension/mod.rs b/type/src/file_extension/mod.rs index d372be7d..b4837b98 100644 --- a/type/src/file_extension/mod.rs +++ b/type/src/file_extension/mod.rs @@ -1,6 +1,9 @@ +mod r#const; mod r#enum; mod r#impl; pub use r#enum::*; +pub(crate) use r#const::*; + use super::*; diff --git a/type/src/hash_set_xx_hash3_64/fn.rs b/type/src/hash_set_xx_hash3_64/fn.rs index 1383373f..7749bc19 100644 --- a/type/src/hash_set_xx_hash3_64/fn.rs +++ b/type/src/hash_set_xx_hash3_64/fn.rs @@ -2,9 +2,8 @@ use super::*; /// Creates a new `HashSetXxHash3_64` with the default hasher. /// -/// # Arguments -/// -/// - `K: Eq + Hash` - The type of the elements in the hash set. +/// The element type is fixed by the return type and must satisfy the `Eq` +/// and `Hash` bounds. /// /// # Returns /// diff --git a/type/src/http_status/impl.rs b/type/src/http_status/impl.rs index ed0540a3..ff7dfbed 100644 --- a/type/src/http_status/impl.rs +++ b/type/src/http_status/impl.rs @@ -12,7 +12,7 @@ impl HttpStatus { /// /// # Returns /// - /// - `u16` - The numeric status code. + /// - `ResponseStatusCode` - The numeric status code. pub fn code(&self) -> ResponseStatusCode { match self { Self::Continue => 100, @@ -87,7 +87,7 @@ impl HttpStatus { /// /// # Arguments /// - /// - `u16` - The numeric HTTP status code. + /// - `ResponseStatusCode` - The numeric HTTP status code. /// /// # Returns /// @@ -166,7 +166,7 @@ impl HttpStatus { /// /// # Arguments /// - /// - `AsRef` - The string to compare against. + /// - `C` - The string to compare against, which requires the `AsRef` bound. /// /// # Returns /// @@ -185,7 +185,7 @@ impl Display for HttpStatus { /// /// # Arguments /// - /// - `&mut Formatter` - The formatter to write to. + /// - `&mut Formatter<'_>` - The formatter to write to. /// /// # Returns /// @@ -275,7 +275,7 @@ impl FromStr for HttpStatus { /// /// # Returns /// - /// - `Result` - The parsed status or error. + /// - `Result` - The parsed status or error. fn from_str(code_str: &str) -> Result { if let Ok(code) = code_str.parse::() { match code { diff --git a/type/src/http_url/impl.rs b/type/src/http_url/impl.rs index 5359db94..1472ef5b 100644 --- a/type/src/http_url/impl.rs +++ b/type/src/http_url/impl.rs @@ -1,7 +1,7 @@ use super::*; /// Implements the `std::error::Error` trait for `HttpUrlError`. -impl std::error::Error for HttpUrlError {} +impl Error for HttpUrlError {} /// Implements the `Display` trait for `HttpUrlError`, allowing it to be formatted as a string. impl Display for HttpUrlError { @@ -49,11 +49,11 @@ impl HttpUrlComponents { /// /// # Arguments /// - /// - `AsRef` - The URL string to parse. + /// - `U` - The URL string to parse, which requires the `AsRef` bound. /// /// # Returns /// - /// - `Result` - Either the parsed components or an error. + /// - `Result` - Either the parsed components or an error. #[inline] pub fn parse(url: U) -> Result where diff --git a/type/src/lib.rs b/type/src/lib.rs index b326f854..dc517d41 100644 --- a/type/src/lib.rs +++ b/type/src/lib.rs @@ -42,6 +42,7 @@ use std::{ any::Any, cell::{RefCell, RefMut}, collections::{HashMap, HashSet, VecDeque}, + error::Error, fmt::{self, Debug, Display, Formatter, Write}, hash::Hash, io::{self, ErrorKind}, @@ -51,7 +52,7 @@ use std::{ pin::Pin, rc::Rc, result::Result, - str::{FromStr, SplitWhitespace}, + str::{FromStr, SplitWhitespace, from_utf8}, sync::{ Arc, atomic::{self, AtomicBool, AtomicUsize}, diff --git a/type/src/request/impl.rs b/type/src/request/impl.rs index 03c49139..fc7185fa 100644 --- a/type/src/request/impl.rs +++ b/type/src/request/impl.rs @@ -1,7 +1,7 @@ use super::*; /// Implements the `std::error::Error` trait for `RequestError`. -impl std::error::Error for RequestError {} +impl Error for RequestError {} /// Provides a default value for `RequestError`. impl Default for RequestError { @@ -18,18 +18,18 @@ impl Default for RequestError { /// /// Maps connection reset and aborted errors to `ClientDisconnected`, /// all other I/O errors are mapped to `ReadConnection`. -impl From for RequestError { +impl From for RequestError { /// Converts an I/O error to a `RequestError`. /// /// # Arguments /// - /// - `std::io::Error` - The I/O error to convert. + /// - `io::Error` - The I/O error to convert. /// /// # Returns /// /// - `RequestError` - The corresponding request error. #[inline(always)] - fn from(error: std::io::Error) -> Self { + fn from(error: io::Error) -> Self { let kind: ErrorKind = error.kind(); if kind == ErrorKind::ConnectionReset || kind == ErrorKind::ConnectionAborted { return RequestError::ClientDisconnected(HttpStatus::BadRequest); @@ -200,7 +200,7 @@ impl RequestConfig { /// /// # Arguments /// - /// - `AsRef` - The configuration. + /// - `C` - The configuration, which requires the `AsRef` bound. /// /// # Returns /// @@ -259,6 +259,7 @@ impl RequestConfig { /// /// Returns a new `Request` instance with all fields initialized to their default values. impl Default for Request { + /// Builds the default `Request` with all fields set to their defaults. #[inline(always)] fn default() -> Self { Self { @@ -535,7 +536,8 @@ impl Request { /// /// # Arguments /// - /// - `&mut AsyncBufReadExt + Unpin` - A mutable reference to a buffered reader implementing `AsyncBufReadExt`. + /// - `&mut R` - A mutable reference to a buffered reader, which requires the + /// `AsyncBufReadExt + Unpin` bound. /// - `&RequestConfig` - Configuration for security limits and buffer settings. /// /// # Returns @@ -601,7 +603,7 @@ impl Request { /// /// # Arguments /// - /// - `&mut AsyncRead + Unpin` - The buffered reader to read from. + /// - `&mut R` - The buffered reader to read from, which requires the `AsyncRead + Unpin` bound. /// - `&mut RequestBody` - The buffer to read the body bytes into. /// - `usize` - The expected content size. /// @@ -631,7 +633,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The query parameter key (implements AsRef). + /// - `K` - The query parameter key, which implements `AsRef`. /// /// # Returns /// @@ -650,7 +652,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The query parameter key (implements AsRef). + /// - `K` - The query parameter key, which implements `AsRef`. /// /// # Returns /// @@ -671,7 +673,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -688,7 +690,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -709,7 +711,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -728,7 +730,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -749,7 +751,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -768,7 +770,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -789,7 +791,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -808,7 +810,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -852,7 +854,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header key to check (must implement AsRef). + /// - `K` - The header key to check, which must implement `AsRef`. /// /// # Returns /// @@ -869,8 +871,8 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The header key to check (must implement AsRef). - /// - `AsRef` - The value to search for (must implement AsRef). + /// - `K` - The header key to check, which must implement `AsRef`. + /// - `V` - The value to search for, which must implement `AsRef`. /// /// # Returns /// @@ -926,7 +928,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The cookie key (implements AsRef). + /// - `K` - The cookie key, which implements `AsRef`. /// /// # Returns /// @@ -947,7 +949,7 @@ impl Request { /// /// # Arguments /// - /// - `AsRef` - The cookie key (implements AsRef). + /// - `K` - The cookie key, which implements `AsRef`. /// /// # Returns /// @@ -999,9 +1001,7 @@ impl Request { /// This method first retrieves the body content as a byte slice using `self.get_body()`. /// It then attempts to deserialize the byte slice as_ref the specified type `T` using `json_from_slice`. /// - /// # Arguments - /// - /// - `DeserializeOwned` - The target type to deserialize as_ref (must implement DeserializeOwned). + /// The target type `T` is a type parameter of this method and must implement `DeserializeOwned`. /// /// # Returns /// @@ -1019,9 +1019,7 @@ impl Request { /// This method first retrieves the body content as a byte slice using `self.get_body()`. /// It then attempts to deserialize the byte slice as_ref the specified type `T` using `json_from_slice`. /// - /// # Arguments - /// - /// - `DeserializeOwned` - The target type to deserialize as_ref (must implement DeserializeOwned). + /// The target type `T` is a type parameter of this method and must implement `DeserializeOwned`. /// /// # Returns /// diff --git a/type/src/response/impl.rs b/type/src/response/impl.rs index bb0320d4..584ac1ee 100644 --- a/type/src/response/impl.rs +++ b/type/src/response/impl.rs @@ -2,23 +2,23 @@ use super::*; /// Implements the `std::error::Error` trait for `ResponseError`. /// This allows `ResponseError` to be treated as a standard Rust error type. -impl std::error::Error for ResponseError {} +impl Error for ResponseError {} /// Converts an I/O error to a `ResponseError`. /// /// Maps I/O errors to `Send` variant with the error message. -impl From for ResponseError { +impl From for ResponseError { /// Converts an I/O error to a `ResponseError`. /// /// # Arguments /// - /// - `std::io::Error` - The I/O error to convert. + /// - `io::Error` - The I/O error to convert. /// /// # Returns /// /// - `ResponseError` - The corresponding response error as `Send`. #[inline(always)] - fn from(error: std::io::Error) -> Self { + fn from(error: io::Error) -> Self { ResponseError::Send(error.to_string()) } } @@ -30,7 +30,7 @@ impl Display for ResponseError { /// /// # Arguments /// - /// - `f` - A mutable reference to a `Formatter` used for writing the formatted string. + /// - `&mut Formatter<'_>` - The formatter used for writing the formatted string. /// /// # Returns /// @@ -58,6 +58,7 @@ impl Display for ResponseError { /// /// Returns a new `Response` instance with all fields initialized to their default values. impl Default for Response { + /// Builds the default `Response` with all fields set to their defaults. #[inline(always)] fn default() -> Self { let http_status: HttpStatus = HttpStatus::default(); @@ -91,7 +92,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -108,7 +109,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -129,7 +130,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -148,7 +149,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -169,7 +170,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -188,7 +189,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header's key (must implement AsRef). + /// - `K` - The header's key, which must implement `AsRef`. /// /// # Returns /// @@ -209,7 +210,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key to check (must implement AsRef). + /// - `K` - The header key to check, which must implement `AsRef`. /// /// # Returns /// @@ -226,8 +227,8 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key to check (must implement AsRef). - /// - `AsRef` - The value to search for (must implement AsRef). + /// - `K` - The header key to check, which must implement `AsRef`. + /// - `V` - The value to search for, which must implement `AsRef`. /// /// # Returns /// @@ -259,7 +260,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key to count (must implement AsRef). + /// - `K` - The header key to count, which must implement `AsRef`. /// /// # Returns /// @@ -278,7 +279,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key to count (must implement AsRef). + /// - `K` - The header key to count, which must implement `AsRef`. /// /// # Returns /// @@ -329,9 +330,7 @@ impl Response { /// This method first retrieves the body content as a byte slice using `self.get_body()`. /// It then attempts to deserialize the byte slice as_ref the specified type `T` using `json_from_slice`. /// - /// # Arguments - /// - /// - `DeserializeOwned` - The target type to deserialize as_ref (must implement DeserializeOwned). + /// The target type `T` is a type parameter of this method and must implement `DeserializeOwned`. /// /// # Returns /// @@ -348,9 +347,7 @@ impl Response { /// This method first retrieves the body content as a byte slice using `self.get_body()`. /// It then attempts to deserialize the byte slice as_ref the specified type `T` using `json_from_slice`. /// - /// # Arguments - /// - /// - `DeserializeOwned` - The target type to deserialize as_ref (must implement DeserializeOwned). + /// The target type `T` is a type parameter of this method and must implement `DeserializeOwned`. /// /// # Returns /// @@ -368,8 +365,13 @@ impl Response { /// Determines whether the header should be skipped during setting. /// - /// - Returns `true` if the header is empty or not allowed. - /// - Returns `false` if the header can be set. + /// # Arguments + /// + /// - `&ResponseHeadersKey` - The header key to inspect. + /// + /// # Returns + /// + /// - `bool` - `true` if the header is empty or not allowed, `false` if the header can be set. #[inline(always)] fn should_skip_header(&self, key: &ResponseHeadersKey) -> bool { key.trim().is_empty() || key == CONTENT_LENGTH @@ -381,8 +383,8 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key (must implement AsRef). - /// - `AsRef` - The header value (must implement AsRef). + /// - `K` - The header key, which must implement `AsRef`. + /// - `V` - The header value, which must implement `AsRef`. /// /// # Returns /// @@ -405,8 +407,8 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key (must implement AsRef). - /// - `AsRef` - The header value (must implement AsRef). + /// - `K` - The header key, which must implement `AsRef`. + /// - `V` - The header value, which must implement `AsRef`. /// /// # Returns /// @@ -434,8 +436,8 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key (must implement AsRef). - /// - `AsRef` - The header value (must implement AsRef). + /// - `K` - The header key, which must implement `AsRef`. + /// - `V` - The header value, which must implement `AsRef`. /// /// # Returns /// @@ -463,7 +465,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key to remove (must implement AsRef). + /// - `K` - The header key to remove, which must implement `AsRef`. /// /// # Returns /// @@ -485,8 +487,8 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The header key (must implement AsRef). - /// - `AsRef` - The value to remove (must implement AsRef). + /// - `K` - The header key, which must implement `AsRef`. + /// - `V` - The value to remove, which must implement `AsRef`. /// /// # Returns /// @@ -576,7 +578,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The cookie key (implements AsRef). + /// - `K` - The cookie key, which implements `AsRef`. /// /// # Returns /// @@ -597,7 +599,7 @@ impl Response { /// /// # Arguments /// - /// - `AsRef` - The cookie key (implements AsRef). + /// - `K` - The cookie key, which implements `AsRef`. /// /// # Returns /// diff --git a/type/src/stream/impl.rs b/type/src/stream/impl.rs index bf9b930a..dd55dc8d 100644 --- a/type/src/stream/impl.rs +++ b/type/src/stream/impl.rs @@ -32,7 +32,7 @@ impl<'a> From for &'a mut Stream { /// /// # Returns /// - /// - `&mut Stream` - A mutable reference to the `Stream` at the given address. + /// - `&'a mut Stream` - A mutable reference to the `Stream` at the given address. /// /// # Safety /// @@ -84,7 +84,7 @@ impl AsRef for Stream { /// /// # Returns /// - /// - `&Stream` - A reference to the `Stream` instance. + /// - `&Self` - A reference to the `Stream` instance. #[inline(always)] fn as_ref(&self) -> &Self { let address: usize = self.into(); @@ -98,7 +98,7 @@ impl AsMut for Stream { /// /// # Returns /// - /// - `&mut Stream` - A mutable reference to the `Stream` instance. + /// - `&mut Self` - A mutable reference to the `Stream` instance. #[inline(always)] fn as_mut(&mut self) -> &mut Self { let address: usize = self.into(); @@ -337,7 +337,7 @@ impl Stream { /// /// # Returns /// - /// - `Result` - The parsed WebSocket request or an error. + /// - `Result` - The parsed WebSocket request or an error. pub async fn try_get_websocket_request(&mut self) -> Result { if self.get_closed() { return Err(RequestError::ServerClosedConnection(HttpStatus::BadRequest)); @@ -440,7 +440,7 @@ impl Stream { /// /// # Arguments /// - /// - `AsRef<[u8]>` - The data to send (must implement AsRef<[u8]>). + /// - `D` - The data to send, which must implement `AsRef<[u8]>`. /// /// # Returns /// @@ -459,7 +459,7 @@ impl Stream { /// /// # Arguments /// - /// - `AsRef<[u8]>` - The data to send (must implement AsRef<[u8]>). + /// - `D` - The data to send, which must implement `AsRef<[u8]>`. /// /// # Panics /// @@ -475,7 +475,8 @@ impl Stream { /// /// # Arguments /// - /// - `IntoIterator>` - The data list to send. + /// - `I` - The data list to send, which must implement `IntoIterator` with + /// `D` requiring the `AsRef<[u8]>` bound. /// /// # Returns /// @@ -499,7 +500,8 @@ impl Stream { /// /// # Arguments /// - /// - `IntoIterator>` - The data list to send. + /// - `I` - The data list to send, which must implement `IntoIterator` with + /// `D` requiring the `AsRef<[u8]>` bound. /// /// # Panics /// diff --git a/type/src/task/impl.rs b/type/src/task/impl.rs index 17e76385..56099957 100644 --- a/type/src/task/impl.rs +++ b/type/src/task/impl.rs @@ -85,7 +85,8 @@ impl Task { /// /// - `Option` - An optional index to force selection of a specific worker. /// If None, the worker is selected using round-robin distribution. - /// - `Future + Send + 'static` - The future to spawn on the task pool. + /// - `F` - The future to spawn on the task pool, which requires the + /// `Future + Send + 'static` bound. /// /// # Returns /// diff --git a/type/src/upgrade_type/impl.rs b/type/src/upgrade_type/impl.rs index e35dbbd8..2381c8e6 100644 --- a/type/src/upgrade_type/impl.rs +++ b/type/src/upgrade_type/impl.rs @@ -55,8 +55,8 @@ impl FromStr for UpgradeType { /// /// # Returns /// - /// - `Ok(UpgradeType)` - The parsed `UpgradeType` variant. - /// - `Err(())` - If parsing fails (though this implementation always returns `Ok`). + /// - `Result` - The parsed `UpgradeType` variant; this + /// implementation always returns `Ok`. #[inline(always)] fn from_str(from_str: &str) -> Result { match from_str.to_ascii_lowercase().as_str() { diff --git a/type/src/websocket_frame/impl.rs b/type/src/websocket_frame/impl.rs index 369310b3..91068813 100644 --- a/type/src/websocket_frame/impl.rs +++ b/type/src/websocket_frame/impl.rs @@ -162,14 +162,12 @@ impl WebSocketFrame { /// /// # Arguments /// - /// - `AsRef<[u8]>` - The raw data to decode into a WebSocket frame. + /// - `D` - The raw data to decode into a WebSocket frame, which requires the `AsRef<[u8]>` bound. /// /// # Returns /// - /// - `Option<(WebSocketFrame, usize)>` - /// - `Some((WebSocketFrame, usize))` - If the frame is successfully decoded, returns the decoded frame - /// and the number of bytes consumed from the input slice. - /// - `None` - If the frame is incomplete or malformed. + /// - `Option<(WebSocketFrame, usize)>` - `Some((frame, consumed))` when the frame is + /// successfully decoded, otherwise `None` when the frame is incomplete or malformed. pub fn decode_ws_frame(data: D) -> Option<(WebSocketFrame, usize)> where D: AsRef<[u8]>, @@ -235,7 +233,8 @@ impl WebSocketFrame { /// /// # Arguments /// - /// - `AsRef<[u8]>` - A reference to a response body (payload) as a byte slice. + /// - `D` - A reference to a response body (payload) as a byte slice, which requires the + /// `AsRef<[u8]>` bound. /// /// # Returns /// @@ -250,7 +249,7 @@ impl WebSocketFrame { let mut frames_list: Vec = Vec::with_capacity((total_len / MAX_FRAME_SIZE) + 1); let mut is_first_frame: bool = true; - let is_valid_utf8: bool = std::str::from_utf8(data_ref).is_ok(); + let is_valid_utf8: bool = from_utf8(data_ref).is_ok(); let base_opcode: WebSocketOpcode = if is_valid_utf8 { WebSocketOpcode::Text } else { @@ -301,11 +300,11 @@ impl WebSocketFrame { /// /// # Arguments /// - /// - `AsRef<[u8]>` - The input data to be hashed. + /// - `D` - The input data to be hashed, which requires the `AsRef<[u8]>` bound. /// /// # Returns /// - /// - `[u8; 20]` - A 20-byte array representing the SHA-1 hash of the input data. + /// A 20-byte array representing the SHA-1 hash of the input data. pub fn sha1(data: D) -> [u8; 20] where D: AsRef<[u8]>, @@ -377,7 +376,8 @@ impl WebSocketFrame { /// /// # Arguments /// - /// - `AsRef` - The client-provided key (typically from the `Sec-WebSocket-Key` header). + /// - `K` - The client-provided key (typically from the `Sec-WebSocket-Key` header), which + /// requires the `AsRef` bound. /// /// # Returns /// @@ -403,11 +403,12 @@ impl WebSocketFrame { /// /// # Arguments /// - /// - `AsRef` - The client-provided key (typically from the `Sec-WebSocket-Key` header). + /// - `K` - The client-provided key (typically from the `Sec-WebSocket-Key` header), which + /// requires the `AsRef` bound. /// /// # Returns /// - /// - `Option` - An optional string representing the generated WebSocket accept key (typically for the `Sec-WebSocket-Accept` header). + /// - `String` - The generated WebSocket accept key (typically for the `Sec-WebSocket-Accept` header). /// /// # Panics /// @@ -429,7 +430,7 @@ impl WebSocketFrame { /// /// # Arguments /// - /// - `AsRef<[u8]>` - The data to encode in base64. + /// - `D` - The data to encode in base64, which requires the `AsRef<[u8]>` bound. /// /// # Returns /// @@ -463,7 +464,7 @@ impl WebSocketFrame { /// /// # Arguments /// - /// - `AsRef<[u8]>` - The data to encode in base64. + /// - `D` - The data to encode in base64, which requires the `AsRef<[u8]>` bound. /// /// # Returns /// diff --git a/type/tests/cookie/fn.rs b/type/tests/cookie/fn.rs index dd81e9a7..6cd7b079 100644 --- a/type/tests/cookie/fn.rs +++ b/type/tests/cookie/fn.rs @@ -6,7 +6,7 @@ fn test_cookie_builder_new() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); @@ -20,7 +20,7 @@ fn test_cookie_builder_default() { assert_eq!(cookie.get_name(), ""); assert_eq!(cookie.get_value(), ""); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); @@ -34,7 +34,7 @@ fn test_cookie_builder_parse_basic() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); @@ -52,7 +52,7 @@ fn test_cookie_builder_parse_with_expires() { *cookie.try_get_expires(), Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string()) ); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); @@ -66,7 +66,7 @@ fn test_cookie_builder_parse_with_max_age() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), Some(3600)); + assert_eq!(cookie.try_get_max_age(), Some(3600)); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); @@ -80,7 +80,7 @@ fn test_cookie_builder_parse_with_domain() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), Some("example.com".to_string())); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); @@ -94,7 +94,7 @@ fn test_cookie_builder_parse_with_path() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), Some("/admin".to_string())); assert!(cookie.try_get_secure().is_none()); @@ -108,10 +108,10 @@ fn test_cookie_builder_parse_with_secure() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); - assert_eq!(*cookie.try_get_secure(), Some(true)); + assert_eq!(cookie.try_get_secure(), Some(true)); assert!(cookie.try_get_http_only().is_none()); assert_eq!(*cookie.try_get_same_site(), None); } @@ -122,11 +122,11 @@ fn test_cookie_builder_parse_with_http_only() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); - assert_eq!(*cookie.try_get_http_only(), Some(true)); + assert_eq!(cookie.try_get_http_only(), Some(true)); assert_eq!(*cookie.try_get_same_site(), None); } @@ -136,7 +136,7 @@ fn test_cookie_builder_parse_with_same_site() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); @@ -155,11 +155,11 @@ fn test_cookie_builder_parse_complex() { *cookie.try_get_expires(), Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string()) ); - assert_eq!(*cookie.try_get_max_age(), Some(3600)); + assert_eq!(cookie.try_get_max_age(), Some(3600)); assert_eq!(*cookie.try_get_domain(), Some("example.com".to_string())); assert_eq!(*cookie.try_get_path(), Some("/admin".to_string())); - assert_eq!(*cookie.try_get_secure(), Some(true)); - assert_eq!(*cookie.try_get_http_only(), Some(true)); + assert_eq!(cookie.try_get_secure(), Some(true)); + assert_eq!(cookie.try_get_http_only(), Some(true)); assert_eq!(*cookie.try_get_same_site(), Some("lax".to_string())); } @@ -169,7 +169,7 @@ fn test_cookie_builder_parse_empty_string() { assert_eq!(cookie.get_name(), ""); assert_eq!(cookie.get_value(), ""); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), None); assert_eq!(*cookie.try_get_path(), None); assert!(cookie.try_get_secure().is_none()); @@ -185,11 +185,11 @@ fn test_cookie_builder_parse_case_insensitive() { assert_eq!(cookie.get_name(), "session_id"); assert_eq!(cookie.get_value(), "abc123"); assert_eq!(*cookie.try_get_expires(), None); - assert_eq!(*cookie.try_get_max_age(), None); + assert_eq!(cookie.try_get_max_age(), None); assert_eq!(*cookie.try_get_domain(), Some("example.com".to_string())); assert_eq!(*cookie.try_get_path(), None); - assert_eq!(*cookie.try_get_secure(), Some(true)); - assert_eq!(*cookie.try_get_http_only(), Some(true)); + assert_eq!(cookie.try_get_secure(), Some(true)); + assert_eq!(cookie.try_get_http_only(), Some(true)); assert_eq!(*cookie.try_get_same_site(), Some("Strict".to_string())); } @@ -207,7 +207,7 @@ fn test_cookie_builder_expires() { fn test_cookie_builder_max_age() { let mut cookie: CookieBuilder = CookieBuilder::new("test", "value"); cookie.set_max_age(3600); - assert_eq!(*cookie.try_get_max_age(), Some(3600)); + assert_eq!(cookie.try_get_max_age(), Some(3600)); } #[test] @@ -228,14 +228,14 @@ fn test_cookie_builder_path() { fn test_cookie_builder_secure() { let mut cookie: CookieBuilder = CookieBuilder::new("test", "value"); cookie.secure(); - assert_eq!(*cookie.try_get_secure(), Some(true)); + assert_eq!(cookie.try_get_secure(), Some(true)); } #[test] fn test_cookie_builder_http_only() { let mut cookie: CookieBuilder = CookieBuilder::new("test", "value"); cookie.http_only(); - assert_eq!(*cookie.try_get_http_only(), Some(true)); + assert_eq!(cookie.try_get_http_only(), Some(true)); } #[test] @@ -262,11 +262,11 @@ fn test_cookie_builder_chaining() { *cookie.try_get_expires(), Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string()) ); - assert_eq!(*cookie.try_get_max_age(), Some(3600)); + assert_eq!(cookie.try_get_max_age(), Some(3600)); assert_eq!(*cookie.try_get_domain(), Some("example.com".to_string())); assert_eq!(*cookie.try_get_path(), Some("/admin".to_string())); - assert_eq!(*cookie.try_get_secure(), Some(true)); - assert_eq!(*cookie.try_get_http_only(), Some(true)); + assert_eq!(cookie.try_get_secure(), Some(true)); + assert_eq!(cookie.try_get_http_only(), Some(true)); assert_eq!(*cookie.try_get_same_site(), Some("Strict".to_string())); } diff --git a/type/tests/lifetime/impl.rs b/type/tests/lifetime/impl.rs index c160d4e2..52f53a27 100644 --- a/type/tests/lifetime/impl.rs +++ b/type/tests/lifetime/impl.rs @@ -7,8 +7,8 @@ impl Lifetime for TestLifetimeStruct { } unsafe fn leak_mut(&self) -> &'static mut Self { let mut boxed: Box = Box::new(Self { value: self.value }); - let reference: *mut Self = std::ptr::addr_of_mut!(*boxed); - std::mem::forget(boxed); + let reference: *mut Self = ptr::addr_of_mut!(*boxed); + mem::forget(boxed); unsafe { &mut *reference } } } diff --git a/type/tests/mod.rs b/type/tests/mod.rs index 15dd7a85..98b1880e 100644 --- a/type/tests/mod.rs +++ b/type/tests/mod.rs @@ -29,8 +29,10 @@ use http_type::*; use std::{ collections::VecDeque, - io::ErrorKind, + io::{self, ErrorKind}, + mem, num::ParseIntError, + ptr, rc::Rc, sync::{ Arc, diff --git a/type/tests/request/fn.rs b/type/tests/request/fn.rs index d48cf5dc..c006c8f6 100644 --- a/type/tests/request/fn.rs +++ b/type/tests/request/fn.rs @@ -214,10 +214,10 @@ fn request_error_default() { #[test] fn request_error_from_io_error() { - let io_error: std::io::Error = std::io::Error::new(ErrorKind::ConnectionReset, "reset"); + let io_error: io::Error = io::Error::new(ErrorKind::ConnectionReset, "reset"); let request_error: RequestError = RequestError::from(io_error); assert!(matches!(request_error, RequestError::ClientDisconnected(_))); - let io_error: std::io::Error = std::io::Error::other("other"); + let io_error: io::Error = io::Error::other("other"); let request_error: RequestError = RequestError::from(io_error); assert!(matches!(request_error, RequestError::ReadConnection(_))); } diff --git a/type/tests/response/fn.rs b/type/tests/response/fn.rs index 134b86d8..4b9773c0 100644 --- a/type/tests/response/fn.rs +++ b/type/tests/response/fn.rs @@ -157,7 +157,7 @@ fn response_error_display() { #[test] fn response_error_from_io() { - let io_error: std::io::Error = std::io::Error::other("test error"); + let io_error: io::Error = io::Error::other("test error"); let response_error: ResponseError = ResponseError::from(io_error); assert!(matches!(response_error, ResponseError::Send(_))); }