From ed590552c7ce50c8f4af603824f8c5fcad3718bb Mon Sep 17 00:00:00 2001 From: Paul Campbell Date: Thu, 21 Nov 2024 14:32:30 +0000 Subject: [PATCH] feat(net): mock request builder adds .with and .with_{option,result} Support for specifying conditional clauses fluently. --- src/net/mod.rs | 4 +- src/net/system.rs | 188 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 3 deletions(-) diff --git a/src/net/mod.rs b/src/net/mod.rs index e2da8e4..8d28277 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -146,10 +146,8 @@ mod system; pub use result::{Error, Result}; -pub use system::{MockNet, Net}; +pub use system::{MockNet, Net, ReqBuilder, WithOption, WithResult}; -pub use http::HeaderMap; -pub use http::Method; pub use http::StatusCode; pub use reqwest::Client; pub use reqwest::Error as RequestError; diff --git a/src/net/system.rs b/src/net/system.rs index 9c57e3f..5d10248 100644 --- a/src/net/system.rs +++ b/src/net/system.rs @@ -240,6 +240,102 @@ impl<'net> ReqBuilder<'net> { } } + /// Use the function to modify the request in-line. + /// + /// # Example + /// + /// ```rust + /// # use kxio::net::Result; + /// # async fn run() -> Result<()> { + /// let net = kxio::net::new(); + /// let response= net + /// .get("http://localhost/") + /// .with(|request| add_std_headers(request)) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// fn add_std_headers(request: kxio::net::ReqBuilder) -> kxio::net::ReqBuilder { + /// request + /// .header("ClientVersion", "1.23.45") + /// .header("Agent", "MyApp/1.1") + /// } + /// ```` + #[must_use] + pub fn with(self, f: impl FnOnce(Self) -> Self) -> Self { + f(self) + } + + /// Starts an Option clause for the supplied option. + /// + /// Must be followed by [WithOption::some], [WithOption::none] or [WithOption::either] + /// to resume the with the [ReqBuilder]. + /// + /// # Example + /// + /// ```rust + /// # use kxio::net::Result; + /// # async fn run() -> Result<()> { + /// let net = kxio::net::new(); + /// let response= net + /// .get("http://localhost/") + /// .with_option(Some("value")) + /// .some(|request, value| request.header("optional-header", value)) + /// .with_option(Some("value")) + /// .none(|request| request.header("special-header", "not-found")) + /// .with_option(Some("value")) + /// .either( + /// /* some */ |request, value| request.header("Setting", value), + /// /* none */ |request| request.header("Setting", "missing") + /// ) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + #[must_use] + pub fn with_option(self, option: Option) -> WithOption<'net, T> { + WithOption { + req_builder: self, + option, + } + } + + /// Starts a Result clause for the supplied result. + /// + /// Must be followed by [WithResult::ok], [WithResult::err] or [WithResult::either] + /// to resume the with the [ReqBuilder]. + /// + /// # Example + /// + /// ```rust + /// # use kxio::net::Result; + /// # async fn run() -> Result<()> { + /// let net = kxio::net::new(); + /// let response = net + /// .get("http://localhost/") + /// .with_result::<&str, ()>(Ok("value")) + /// .ok(|request, value| request.header("good-header", value)) + /// .with_result::<(), &str>(Err("value")) + /// .err(|request, err| request.header("bad-header", err)) + /// .with_result::<&str, &str>(Ok("value")) + /// .either( + /// /* ok */ |request, ok| request.header("Setting", ok), + /// /* err */ |request, err| request.header("SettingError", err) + /// ) + /// .send() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + #[must_use] + pub fn with_result(self, result: std::result::Result) -> WithResult<'net, T, E> { + WithResult { + req_builder: self, + result, + } + } + /// Constructs the Request and sends it to the target URL, returning a /// future Response. /// @@ -653,6 +749,98 @@ impl<'net> WhenRequest<'net, WhenBuildResponse> { } } +/// Option clause for building a request with [ReqBuilder]. +/// +/// See: [ReqBuilder::with_option]. +pub struct WithOption<'net, T> { + req_builder: ReqBuilder<'net>, + option: Option, +} +impl<'net, T> WithOption<'net, T> { + /// Handles when the preceeding [Option] is [Some]. + /// + /// The function is passed the [ReqBuilder] and the value in the [Some]. + /// + /// Returns the [ReqBuilder]. + pub fn some( + self, + f_some: impl FnOnce(ReqBuilder<'net>, T) -> ReqBuilder<'net>, + ) -> ReqBuilder<'net> { + match self.option { + Some(value) => f_some(self.req_builder, value), + None => self.req_builder, + } + } + + /// Handles when the preceeding [Option] is [None]. + /// + /// The function is passed the [ReqBuilder]. + /// + /// Returns the [ReqBuilder]. + pub fn none( + self, + f_none: impl FnOnce(ReqBuilder<'net>) -> ReqBuilder<'net>, + ) -> ReqBuilder<'net> { + match self.option { + None => f_none(self.req_builder), + Some(_) => self.req_builder, + } + } + + /// Handles the preceeding [Option]. + /// + /// If the [Option] is [Some], then the `f_some` function is passed the [ReqBuilder] and the value in the [Some]. + /// + /// If the [Option] is [None], then the `f_none` function is passed the [ReqBuilder]. + /// + /// Returns the [ReqBuilder]. + pub fn either( + self, + f_some: impl FnOnce(ReqBuilder<'net>, T) -> ReqBuilder<'net>, + f_none: impl FnOnce(ReqBuilder<'net>) -> ReqBuilder<'net>, + ) -> ReqBuilder<'net> { + match self.option { + Some(value) => f_some(self.req_builder, value), + None => f_none(self.req_builder), + } + } +} + +pub struct WithResult<'net, T, E> { + req_builder: ReqBuilder<'net>, + result: std::result::Result, +} +impl<'net, T, E> WithResult<'net, T, E> { + pub fn ok( + self, + f_ok: impl FnOnce(ReqBuilder<'net>, T) -> ReqBuilder<'net>, + ) -> ReqBuilder<'net> { + match self.result { + Ok(ok) => f_ok(self.req_builder, ok), + Err(_) => self.req_builder, + } + } + pub fn err( + self, + f_err: impl FnOnce(ReqBuilder<'net>, E) -> ReqBuilder<'net>, + ) -> ReqBuilder<'net> { + match self.result { + Err(err) => f_err(self.req_builder, err), + Ok(_) => self.req_builder, + } + } + pub fn either( + self, + f_ok: impl FnOnce(ReqBuilder<'net>, T) -> ReqBuilder<'net>, + f_err: impl FnOnce(ReqBuilder<'net>, E) -> ReqBuilder<'net>, + ) -> ReqBuilder<'net> { + match self.result { + Ok(ok) => f_ok(self.req_builder, ok), + Err(err) => f_err(self.req_builder, err), + } + } +} + #[cfg(test)] mod tests { use super::*;