### Quick Start: Embedded Client Source: https://docs.rs/ferrotunnel/latest/ferrotunnel Embedded client example from the Quick Start section. It builds a client with server address, token, and local address, starts it, prints the session ID, and waits for Ctrl+C before shutting down. Requires a running server and a valid token. ```rust use ferrotunnel::Client; #[tokio::main] async fn main() -> ferrotunnel::Result<()> { let mut client = Client::builder() .server_addr("tunnel.example.com:7835") .token("my-secret-token") .local_addr("127.0.0.1:8080") .build()?; let info = client.start().await?; println!("Connected! Session: {:?}", info.session_id()); // Keep running until Ctrl+C tokio::signal::ctrl_c().await?; client.shutdown().await?; Ok(()) } ``` -------------------------------- ### Quick Start: Embedded Server Source: https://docs.rs/ferrotunnel/latest/ferrotunnel Embedded server example from the Quick Start section. It builds a server with tunnel bind, HTTP bind, and token, starts it, waits for Ctrl+C, then shuts down. Requires the same token as the client. ```rust use ferrotunnel::Server; #[tokio::main] async fn main() -> ferrotunnel::Result<()> { let mut server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .http_bind("0.0.0.0:8080".parse().expect("valid HTTP bind")) .token("my-secret-token") .build()?; server.start().await?; tokio::signal::ctrl_c().await?; server.shutdown().await?; Ok(()) } ``` -------------------------------- ### Client builder example with server_addr, token, local_addr Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/client.rs.html Shows the primary usage of the `Client` builder: configure server address, token, and local address, then start the tunnel and print the session ID. The example is marked `no_run` and wrapped in an async function returning `ferrotunnel::Result<()>`. ```rust //! Embeddable tunnel client with builder pattern. //! //! # Example //! //! ```rust,no_run //! use ferrotunnel::Client; //! //! # async fn example() -> ferrotunnel::Result<()> { //! let mut client = Client::builder() //! .server_addr("tunnel.example.com:7835") //! .token("my-secret-token") //! .local_addr("127.0.0.1:8080") //! .build()?; //! //! let info = client.start().await?; //! println!("Connected! Session: {:?}", info.session_id()); //! # Ok(()) //! # } //! ``` ``` -------------------------------- ### Embeddable tunnel server example with builder pattern Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Shows how to create and start a tunnel server using the builder pattern. It binds to a tunnel address and an HTTP address, sets a token, starts the server, waits for Ctrl+C, and then shuts down. ```rust use ferrotunnel::Server; # async fn example() -> ferrotunnel::Result<()> { let mut server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .http_bind("0.0.0.0:8080".parse().expect("valid HTTP bind")) .token("my-secret-token") .build()?; server.start().await?; tokio::signal::ctrl_c().await?; server.shutdown().await?; # Ok(()) # } ``` -------------------------------- ### Server builder example with bind and token Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Shows how to create a server builder with a bind address and token, then build the server. This example is from the `Server::builder()` documentation. ```rust use ferrotunnel::Server; let server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .token("secret") .build() .expect("valid server configuration"); ``` -------------------------------- ### Client::builder() example Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/client/struct.Client.html Example from the `builder()` method documentation. Creates a new client builder, sets the server address and token, then builds the client. The `expect` call panics if the configuration is invalid. ```rust use ferrotunnel::Client; let client = Client::builder() .server_addr("localhost:7835") .token("secret") .build() .expect("valid client configuration"); ``` -------------------------------- ### Quick Start: Embedded Server Source: https://docs.rs/ferrotunnel Demonstrates how to initialize and start a FerroTunnel server using the builder pattern to bind tunnel and HTTP ports. ```rust use ferrotunnel::Server; #[tokio::main] async fn main() -> ferrotunnel::Result<()> { let mut server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .http_bind("0.0.0.0:8080".parse().expect("valid HTTP bind")) .token("my-secret-token") .build()?; server.start().await?; tokio::signal::ctrl_c().await?; server.shutdown().await?; Ok(()) } ``` -------------------------------- ### Quick Start: Embedded Client Source: https://docs.rs/ferrotunnel Demonstrates how to initialize and start a FerroTunnel client using the builder pattern to connect to a remote server. ```rust use ferrotunnel::Client; #[tokio::main] async fn main() -> ferrotunnel::Result<()> { let mut client = Client::builder() .server_addr("tunnel.example.com:7835") .token("my-secret-token") .local_addr("127.0.0.1:8080") .build()?; let info = client.start().await?; println!("Connected! Session: {:?}", info.session_id()); // Keep running until Ctrl+C tokio::signal::ctrl_c().await?; client.shutdown().await?; Ok(()) } ``` -------------------------------- ### start() error on unreachable server with auto_reconnect Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/client.rs.html Configures auto-reconnect with a short delay and a startup timeout, then starts against an unreachable address. Expects start() to return an error within the test deadline and the client to not be running. ```rust let mut client = Client::builder() .server_addr("127.0.0.1:1") .token("secret") .auto_reconnect(true) .reconnect_delay(Duration::from_millis(50)) .startup_timeout(Some(Duration::from_millis(300))) .build() .expect("should build"); let result = tokio::time::timeout(Duration::from_secs(5), client.start()).await; let inner = result.expect("start() should resolve within the test deadline"); assert!( inner.is_err(), "start() should return an error when the server is unreachable" ); assert!(!client.is_running()); ``` -------------------------------- ### Build and start a tunnel client with Client::builder() Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/client/index.html Constructs a client using the builder pattern, setting the server address, token, and local address, then starts it and prints the session ID. Requires a valid server address and token. ```rust use ferrotunnel::Client; let mut client = Client::builder() .server_addr("tunnel.example.com:7835") .token("my-secret-token") .local_addr("127.0.0.1:8080") .build()?; let info = client.start().await?; println!("Connected! Session: {:?}", info.session_id()); ``` -------------------------------- ### Test server start/shutdown lifecycle Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Tests the full server lifecycle: start, verify running, attempt a second start (which must fail with "already started"), then shutdown and verify not running. ```rust let mut server = Server::builder() .bind("127.0.0.1:0".parse().unwrap()) .http_bind("127.0.0.1:0".parse().unwrap()) .token("secret") .build() .expect("should build"); server.start().await.expect("server should start"); assert!(server.is_running()); let err = server .start() .await .expect_err("second start should fail while running"); assert!(err.to_string().contains("already started")); server.shutdown().await.expect("server should shut down"); assert!(!server.is_running()); ``` -------------------------------- ### ClientConfig::startup_timeout Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/config/struct.ClientConfig.html Returns the maximum time `start()` waits for the initial connection. `None` waits indefinitely. ```APIDOC ## ClientConfig::startup_timeout ### Description Returns the maximum time `start()` waits for the initial connection. `None` waits indefinitely. ### Method startup_timeout ### Signature pub fn startup_timeout(&self) -> Option ### Parameters None ### Returns Option - The maximum wait time, or None to wait indefinitely. ``` -------------------------------- ### FerroTunnel Quick Start: Embedded Client and Server Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/lib.rs.html Two complete examples from the Quick Start sections: the embedded client connects to a tunnel server and forwards a local address, while the embedded server binds tunnel and HTTP ports with a token. Both use the builder API, await Ctrl+C, and shut down cleanly. ```rust use ferrotunnel::Client; #[tokio::main] async fn main() -> ferrotunnel::Result<()> { let mut client = Client::builder() .server_addr("tunnel.example.com:7835") .token("my-secret-token") .local_addr("127.0.0.1:8080") .build()?; let info = client.start().await?; println!("Connected! Session: {:?}", info.session_id()); // Keep running until Ctrl+C tokio::signal::ctrl_c().await?; client.shutdown().await?; Ok(()) } ``` ```rust use ferrotunnel::Server; #[tokio::main] async fn main() -> ferrotunnel::Result<()> { let mut server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .http_bind("0.0.0.0:8080".parse().expect("valid HTTP bind")) .token("my-secret-token") .build()?; server.start().await?; tokio::signal::ctrl_c().await?; server.shutdown().await?; Ok(()) } ``` -------------------------------- ### Test server not running initially Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Confirms that a newly built server is not running until start() is called. ```rust let server = Server::builder() .token("secret") .build() .expect("should build"); assert!(!server.is_running()); ``` -------------------------------- ### Client::config() Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/client/struct.Client.html Gets the current configuration. ```APIDOC ## Client::config() ### Description Gets the current configuration. ### Method config() ### Signature pub fn config(&self) -> &ClientConfig ``` -------------------------------- ### Product implementation example for Result Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/prelude/type.Result.html Demonstrates using the product method on an iterator of Results, which returns an Err if any element is an Err, or the product of all elements otherwise. ```rust let nums = vec!["5", "10", "1", "2"]; let total: Result = nums.iter().map(|w| w.parse::()).product(); assert_eq!(total, Ok(100)); let nums = vec!["5", "10", "one", "2"]; let total: Result = nums.iter().map(|w| w.parse::()).product(); assert!(total.is_err()); ``` -------------------------------- ### Server::config() Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/server/struct.Server.html Gets the current configuration of the server. ```APIDOC ## Server::config() ### Description Gets the current configuration of the server. ### Method config() ### Signature pub fn config(&self) -> &ServerConfig ### Parameters None ### Returns - **&ServerConfig** - A reference to the server's configuration. ``` -------------------------------- ### Start server with plugin registry, HTTP/3 ingress, and tokio::select! shutdown handling Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Sets up the plugin registry with a default LoggerPlugin, initializes plugins (logging failures but continuing), and constructs the HTTP ingress with optional HTTP/3 support. Spawns tunnel, ingress, and optional HTTP/3 tasks, then waits for shutdown or any service exit using tokio::select!, aborting remaining handles. The service_result helper maps JoinError to TunnelError::Internal and logs the service name. ```rust // Add default plugins registry.register(Arc::new(RwLock::new( ferrotunnel_plugin::builtin::LoggerPlugin::new(), ))); if let Err(e) = registry.init_all().await { tracing::error!("Failed to initialize plugins: {}", e); // We continue starting the server even if plugins fail, but we log it. // Alternatively, we could return the error: return Err(TunnelError::InvalidState(format!("Plugin init failed: {e}"))); } let registry = Arc::new(registry); let sessions = tunnel_server.sessions(); #[cfg(feature = "http3")] let alt_svc_header = config.http3_bind_addr.and_then(|addr| { Http3IngressConfig::default() .alt_svc_header_value(addr) .ok() }); let ingress_config = IngressConfig { response_timeout: config.http_response_timeout, ..Default::default() }; let ingress = HttpIngress::with_config( config.http_bind_addr, sessions.clone(), registry.clone(), ingress_config, ); // Shadow (rather than `mut`) so the binding stays immutable when the // `http3` feature is disabled and `alt_svc_header` does not exist. #[cfg(feature = "http3")] let ingress = match alt_svc_header { Some(value) => ingress.with_alt_svc_header(value), None => ingress, }; // Spawn both services #[cfg(feature = "quic")] let mut tunnel_handle = { let tunnel_bind_addr = config.bind_addr; tokio::spawn(async move { if matches!(tunnel_transport, TransportConfig::Quic(_)) { tunnel_server.run_quic(tunnel_bind_addr).await } else { tunnel_server.run().await } }) }; #[cfg(not(feature = "quic"))] let mut tunnel_handle = tokio::spawn(async move { tunnel_server.run().await }); let mut ingress_handle = tokio::spawn(async move { ingress.start().await }); #[cfg(feature = "http3")] let http3_handle = if let Some(http3_bind_addr) = config.http3_bind_addr { let cert_path = config.http3_cert_path.clone().ok_or_else(|| { TunnelError::Config("HTTP/3 ingress requires certificate path".into()) })?; let key_path = config .http3_key_path .clone() .ok_or_else(|| TunnelError::Config("HTTP/3 ingress requires key path".into()))?; let http3_config = Http3IngressConfig::with_certs( cert_path.to_string_lossy().to_string(), key_path.to_string_lossy().to_string(), ) .response_timeout(config.http_response_timeout); let http3_ingress = Http3Ingress::new(http3_bind_addr, sessions, registry, http3_config); let http3_shutdown_rx = shutdown_rx.clone(); Some(tokio::spawn(async move { http3_ingress.start_with_shutdown(http3_shutdown_rx).await })) } else { None }; // Wait for shutdown or either service to exit #[cfg(feature = "http3")] if let Some(mut http3_handle) = http3_handle { let result = tokio::select! { result = &mut tunnel_handle => Self::service_result("Tunnel", result), result = &mut ingress_handle => Self::service_result("Ingress", result), result = &mut http3_handle => Self::service_result("HTTP/3 ingress", result), changed = shutdown_rx.changed() => { Self::log_shutdown_result(&changed); Ok(()) } }; tunnel_handle.abort(); ingress_handle.abort(); http3_handle.abort(); return result; } let result = tokio::select! { result = &mut tunnel_handle => Self::service_result("Tunnel", result), result = &mut ingress_handle => Self::service_result("Ingress", result), changed = shutdown_rx.changed() => { Self::log_shutdown_result(&changed); Ok(()) } }; tunnel_handle.abort(); ingress_handle.abort(); result } fn service_result( name: &str, result: std::result::Result, tokio::task::JoinError>, ) -> Result<()> { match result { Ok(inner) => inner, Err(e) => { tracing::error!("Service {} failed: {}", name, e); Err(TunnelError::Internal(e.to_string())) } } } ``` -------------------------------- ### Get current server configuration Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Returns a reference to the current server configuration. ```rust pub fn config(&self) -> &ServerConfig { &self.config } ``` -------------------------------- ### is_err() Example Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/prelude/type.Result.html Example for the is_err method, which returns true if the result is Err. ```rust let x: Result = Ok(-3); assert_eq!(x.is_err(), false); let x: Result = Err("Some error message"); assert_eq!(x.is_err(), true); ``` -------------------------------- ### Server::start() Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/server/struct.Server.html Starts the tunnel server in the background. This launches the configured tunnel and ingress services. The server runs until shutdown() or stop() is called, or until one of the service tasks exits. ```APIDOC ## Server::start() ### Description Starts the tunnel server in the background. This launches the configured tunnel and ingress services. The server runs until shutdown() or stop() is called, or until one of the service tasks exits. ### Method start() ### Signature pub async fn start(&mut self) -> Result<()> ### Parameters None ### Returns - **Result<()>** - Ok(()) if the server started successfully, or an error if the server is already running. ### Errors Returns an error if the server is already running. ``` -------------------------------- ### Server::builder() example Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/server/struct.Server.html Creates a new tunnel server using the builder pattern. Binds to address 0.0.0.0:7835, sets a token "secret", and builds the server, panicking on invalid configuration. ```rust use ferrotunnel::Server; let server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .token("secret") .build() .expect("valid server configuration"); ``` -------------------------------- ### Client::start() Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/client/struct.Client.html Starts the tunnel client and connects to the server. Returns TunnelInfo with connection details on success. The client runs until shutdown() is called or the connection fails. If auto_reconnect is enabled, the client will automatically reconnect on connection loss. ```APIDOC ## Client::start() ### Description Starts the tunnel client and connects to the server. Returns TunnelInfo with connection details on success. The client will run until shutdown() is called or the connection fails. If auto_reconnect is enabled, the client will automatically reconnect on connection loss. ### Method start() ### Signature pub async fn start(&mut self) -> Result ### Errors Returns an error if the client is already running. ``` -------------------------------- ### expect: recommended message style Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Use to panic with a message that explains why the `Result` is expected to be `Ok`. The example shows the recommended style using the word "should". ```rust let path = std::env::var("IMPORTANT_PATH") .expect("env variable `IMPORTANT_PATH` should be set by `wrapper_script.sh`"); ``` -------------------------------- ### is_ok_and() on Result Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Example for the is_ok_and method, which returns true if the result is Ok and the value matches a predicate. ```rust let x: Result = Ok(2); assert_eq!(x.is_ok_and(|x| x > 1), true); let x: Result = Ok(0); assert_eq!(x.is_ok_and(|x| x > 1), false); let x: Result = Err("hey"); assert_eq!(x.is_ok_and(|x| x > 1), false); let x: Result = Ok("ownership".to_string()); assert_eq!(x.as_ref().is_ok_and(|x| x.len() > 1), true); println!("still alive {:?}", x); ``` -------------------------------- ### Result::unwrap basic usage and panic Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Shows basic usage of `unwrap` on an `Ok` value and the panic behavior on an `Err` value. The first example asserts the returned value; the second demonstrates the panic message. ```rust let x: Result = Ok(2); assert_eq!(x.unwrap(), 2); ``` ```rust let x: Result = Err("emergency failure"); x.unwrap(); // panics with `emergency failure` ``` -------------------------------- ### as_ref: Convert Result to Result of references Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Illustrates using `as_ref` to convert a `Result` into a `Result` of references, leaving the original unchanged. The example shows both `Ok` and `Err` cases. ```rust let x: Result = Ok(2); assert_eq!(x.as_ref(), Ok(&2)); let x: Result = Err("Error"); assert_eq!(x.as_ref(), Err(&"Error")); ``` -------------------------------- ### Embedded tunnel server with Server::builder() Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/server/index.html Shows the builder pattern for creating and running an embedded tunnel server. Requires a valid bind address, HTTP bind address, and token; the build() call returns a Result that must be handled. After start(), the server runs until ctrl_c() is received, then shutdown() is called. ```rust use ferrotunnel::Server; let mut server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .http_bind("0.0.0.0:8080".parse().expect("valid HTTP bind")) .token("my-secret-token") .build()?; server.start().await?; tokio::signal::ctrl_c().await?; server.shutdown().await?; ``` -------------------------------- ### unwrap: Basic usage returning Ok value Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/prelude/type.Result.html Shows how to use unwrap to get the contained Ok value. This will panic if the Result is Err, so it is generally discouraged in favor of the ? operator or pattern matching. ```rust let x: Result = Ok(2); assert_eq!(x.unwrap(), 2); ``` -------------------------------- ### expect: panic with custom message Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Use to get the `Ok` value or panic with a custom message. The example shows a panic with the message and the `Err` content. Prefer pattern matching or `unwrap_or` variants instead. ```rust let x: Result = Err("emergency failure"); x.expect("Testing expect"); // panics with `Testing expect: emergency failure` ``` -------------------------------- ### iter_mut: mutable iteration over Ok value Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Use to get a mutable iterator over the possibly contained value, allowing modification of the `Ok` value. The example shows modifying the value and the `Err` case yielding `None`. ```rust let mut x: Result = Ok(7); match x.iter_mut().next() { Some(v) => *v = 40, None => {}, } assert_eq!(x, Ok(40)); let mut x: Result = Err("nothing!"); assert_eq!(x.iter_mut().next(), None); ``` -------------------------------- ### as_deref_mut: mutable dereference of Ok value Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Use to get a mutable reference to the `Ok` value's target via `DerefMut`. The example shows both `Ok` and `Err` cases, modifying the `Ok` value in place. ```rust let mut s = "HELLO".to_string(); let mut x: Result = Ok("hello".to_string()); let y: Result<&mut str, &mut u32> = Ok(&mut s); assert_eq!(x.as_deref_mut().map(|x| { x.make_ascii_uppercase(); x }), y); let mut i = 42; let mut x: Result = Err(42); let y: Result<&mut str, &mut u32> = Err(&mut i); assert_eq!(x.as_deref_mut().map(|x| { x.make_ascii_uppercase(); x }), y); ``` -------------------------------- ### as_mut: Mutate contained value in place Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Shows using `as_mut` to get mutable references to the contained `Ok` or `Err` value, allowing in-place modification. The example defines a `mutate` function that changes the value to 42 for `Ok` and 0 for `Err`, then verifies the results. ```rust fn mutate(r: &mut Result) { match r.as_mut() { Ok(v) => *v = 42, Err(e) => *e = 0, } } let mut x: Result = Ok(2); mutate(&mut x); assert_eq!(x.unwrap(), 42); let mut x: Result = Err(13); mutate(&mut x); assert_eq!(x.unwrap_err(), 0); ``` -------------------------------- ### Client builder with startup_timeout(None) Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/client.rs.html Builds a client with no startup timeout and asserts the config stores None. Use when verifying that omitting a startup timeout is accepted and stored as None. ```rust let mut client = Client::builder() .server_addr("localhost:7835") .token("secret") .startup_timeout(None) .build() .expect("should build"); assert_eq!(client.config().startup_timeout, None); ``` -------------------------------- ### Register and initialize plugins with PluginRegistry Source: https://docs.rs/ferrotunnel-plugin/%5E1.5.2 Creates a PluginRegistry, registers a custom plugin wrapped in Arc, and initializes all plugins. Requires tokio runtime and the registry to be mutable. ```rust use ferrotunnel_plugin::PluginRegistry; use std::sync::Arc; use tokio::sync::RwLock; #[tokio::main] async fn main() { let mut registry = PluginRegistry::new(); // Register your custom plugin registry.register(Arc::new(RwLock::new(MyPlugin))); // Initialize all plugins let _ = registry.init_all().await; } ``` -------------------------------- ### is_ok() on Result Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Example for the is_ok method, which returns true if the result is Ok. ```rust let x: Result = Ok(-3); assert_eq!(x.is_ok(), true); let x: Result = Err("Some error message"); assert_eq!(x.is_ok(), false); ``` -------------------------------- ### connect_once: QUIC vs TCP connection with callbacks Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/client.rs.html Establishes a single connection attempt. For QUIC transport, uses connect_and_run_quic_with_callback and spawns a QUIC bridge; otherwise uses connect_and_run_with_callback with the proxy dispatch. Both callbacks publish tunnel info on session ID. ```rust async fn connect_once( mut client: TunnelClient, transport_config: TransportConfig, proxy_dispatch: StreamDispatch, #[cfg(feature = "quic")] local_addr: String, info_tx: TunnelInfoTx, ) -> Result<()> { match transport_config { #[cfg(feature = "quic")] TransportConfig::Quic(_) => { client .connect_and_run_quic_with_callback( move |stream| { let local_addr = local_addr.clone(); async move { Self::spawn_quic_bridge(local_addr, stream); } }, move |session_id| Self::publish_tunnel_info(&info_tx, session_id), ) .await } _ => { client .connect_and_run_with_callback( move |stream| { let proxy_dispatch = proxy_dispatch.clone(); async move { proxy_dispatch(stream); } }, move |session_id| Self::publish_tunnel_info(&info_tx, session_id), ) .await } } } ``` -------------------------------- ### ServerBuilder::build Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/server/struct.ServerBuilder.html Builds the server with the configured options. Returns an error if required configuration is missing or invalid. ```APIDOC ## ServerBuilder::build ### Description Builds the server with the configured options. Returns an error if required configuration is missing or invalid. ### Method `pub fn build(self) -> Result` ### Parameters None. ### Returns `Result` - The constructed `Server` on success, or an error if configuration is invalid. ### Errors Returns an error if: - `token` is not set - any rate-limit value is zero or `bytes_per_sec` exceeds `u32::MAX` - `http_response_timeout` is zero ``` -------------------------------- ### ServerBuilder methods: http_response_timeout, quic, build Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Builder methods for configuring the server: http_response_timeout sets the upstream response timeout (default 60s, zero rejected by build), quic enables QUIC transport (requires TLS 1.3 and certificate/key paths), and build validates configuration and returns a Server or error. ```rust pub fn http_response_timeout(mut self, timeout: Duration) -> Self { self.config.http_response_timeout = timeout; self } ``` ```rust #[cfg(feature = "quic")] #[must_use] pub fn quic(mut self, config: &ferrotunnel_common::QuicConfig) -> Self { if let Some(quic) = ferrotunnel_core::transport::quic::QuicTransportConfig::from_common(config) { self.tls_validation_error = None; self.transport_config = Some(TransportConfig::Quic(quic)); } self } ``` ```rust pub fn build(self) -> Result { self.config.validate()?; if let Some(error) = self.tls_validation_error { return Err(error); } Ok(Server { config: self.config, transport_config: self.transport_config.unwrap_or_default(), shutdown_tx: None, task: None, }) } ``` -------------------------------- ### cloned() on Result<&T, E> Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Example for the cloned method on Result<&T, E>, which clones the Ok value. Requires T: Clone. ```rust let val = 12; let x: Result<&i32, i32> = Ok(&val); assert_eq!(x, Ok(&12)); let cloned = x.cloned(); assert_eq!(cloned, Ok(12)); ``` -------------------------------- ### Startup timeout handling with shutdown and abort Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/client.rs.html Waits for the initial connection result, bounded by an optional startup timeout. On timeout, sends a shutdown signal and aborts the background task, returning a TunnelError::Timeout. ```rust match startup_timeout { Some(timeout) => match tokio::time::timeout(timeout, info_rx).await { Ok(res) => res .map_err(|_| TunnelError::Connection("Failed to establish connection".into())), Err(_elapsed) => { if let Some(tx) = self.shutdown_tx.take() { let _ = tx.send(true); } if let Some(task) = self.task.take() { task.abort(); } Err(TunnelError::Timeout( "timed out waiting for initial connection".into(), )) } }, None => info_rx .await .map_err(|_| TunnelError::Connection("Failed to establish connection".into())), } ``` -------------------------------- ### copied() on Result<&T, E> Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Example for the copied method on Result<&T, E>, which copies the Ok value. Requires T: Copy. ```rust let val = 12; let x: Result<&i32, i32> = Ok(&val); assert_eq!(x, Ok(&12)); let copied = x.copied(); assert_eq!(copied, Ok(12)); ``` -------------------------------- ### unwrap_err_unchecked: undefined behavior on Ok Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Demonstrates calling `unwrap_err_unchecked` on an `Ok`, which is undefined behavior. This example is for illustration only and should not be used in safe code. ```rust let x: Result = Ok(2); unsafe { x.unwrap_err_unchecked() }; // Undefined behavior! ``` -------------------------------- ### Server::builder() Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/server/struct.Server.html Creates a new server builder for configuring and constructing a Server instance. The builder pattern allows setting bind address, token, and other options before building the server. ```APIDOC ## Server::builder() ### Description Creates a new server builder for configuring and constructing a Server instance. ### Method builder() ### Signature pub fn builder() -> ServerBuilder ### Parameters None ### Returns - **ServerBuilder** - A builder for configuring and building a Server. ### Example ```rust use ferrotunnel::Server; let server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .token("secret") .build() .expect("valid server configuration"); ``` ``` -------------------------------- ### unwrap_unchecked: undefined behavior on Err Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Shows calling `unwrap_unchecked` on an `Err`, which is undefined behavior. This example is for illustration only and should not be used in safe code. ```rust let x: Result = Err("emergency failure"); unsafe { x.unwrap_unchecked() }; // Undefined behavior! ``` -------------------------------- ### Client::builder() Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/client/struct.Client.html Creates a new client builder for configuring and building a Client instance. ```APIDOC ## Client::builder() ### Description Creates a new client builder for configuring and building a Client instance. ### Method builder() ### Signature pub fn builder() -> ClientBuilder ### Example ```rust use ferrotunnel::Client; let client = Client::builder() .server_addr("localhost:7835") .token("secret") .build() .expect("valid client configuration"); ``` ``` -------------------------------- ### transpose() on Result, E> Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Example for the transpose method on Result, E>, which converts a Result of an Option into an Option of a Result. ```rust #[derive(Debug, Eq, PartialEq)] struct SomeErr; let x: Result, SomeErr> = Ok(Some(5)); let y: Option> = Some(Ok(5)); assert_eq!(x.transpose(), y); ``` -------------------------------- ### cloned() on Result<&mut T, E> Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Example for the cloned method on Result<&mut T, E>, which clones the Ok value. Requires T: Clone. ```rust let mut val = 12; let x: Result<&mut i32, i32> = Ok(&mut val); assert_eq!(x, Ok(&mut 12)); let cloned = x.cloned(); assert_eq!(cloned, Ok(12)); ``` -------------------------------- ### Client builder TLS with CA only Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/client.rs.html Builds a client with TLS enabled and a CA certificate but no client certificate. Expects build() to succeed and the server address to be non-empty. ```rust let tls = TlsConfig { enabled: true, ca_cert_path: Some(PathBuf::from("ca.pem")), ..Default::default() }; let client = Client::builder() .server_addr("localhost:7835") .token("secret") .tls(&tls) .build() .expect("server-auth TLS should not require client certificate material"); assert!(!client.config().server_addr.is_empty()); ``` -------------------------------- ### copied() on Result<&mut T, E> Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Example for the copied method on Result<&mut T, E>, which copies the Ok value. Requires T: Copy. ```rust let mut val = 12; let x: Result<&mut i32, i32> = Ok(&mut val); assert_eq!(x, Ok(&mut 12)); let copied = x.copied(); assert_eq!(copied, Ok(12)); ``` -------------------------------- ### flatten() on Result, E> Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Example for the flatten method on Result, E>, which removes one level of nesting. ```rust let x: Result, u32> = Ok(Ok("hello")); assert_eq!(Ok("hello"), x.flatten()); let x: Result, u32> = Ok(Err(6)); assert_eq!(Err(6), x.flatten()); let x: Result, u32> = Err(6); assert_eq!(Err(6), x.flatten()); ``` -------------------------------- ### Client builder tests Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/client.rs.html Unit tests for the client builder. Covers successful builds, all options, missing server_addr/token errors, default local_addr, initial not-running state, and startup_timeout default. ```rust #[cfg(test)] mod tests { use super::*; use std::path::PathBuf; #[test] fn test_client_builder_success() { let client = Client::builder() .server_addr("localhost:7835") .token("secret-token") .local_addr("127.0.0.1:9000") .build(); assert!(client.is_ok()); } #[test] fn test_client_builder_with_all_options() { let client = Client::builder() .server_addr("tunnel.example.com:7835") .token("my-token") .local_addr("127.0.0.1:3000") .auto_reconnect(false) .reconnect_delay(Duration::from_secs(10)) .limits(&LimitsConfig { max_frame_bytes: 4096, ..Default::default() }) .build() .expect("should build successfully"); assert_eq!(client.config().server_addr, "tunnel.example.com:7835"); assert_eq!(client.config().token, "my-token"); assert_eq!(client.config().local_addr, "127.0.0.1:3000"); assert!(!client.config().auto_reconnect); assert_eq!(client.config().reconnect_delay, Duration::from_secs(10)); assert_eq!(client.config().limits.max_frame_bytes, 4096); } #[test] fn test_client_builder_missing_server_addr() { let result = Client::builder() .token("secret") .local_addr("127.0.0.1:8080") .build(); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("server_addr")); } #[test] fn test_client_builder_missing_token() { let result = Client::builder() .server_addr("localhost:7835") .local_addr("127.0.0.1:8080") .build(); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("token")); } #[test] fn test_client_builder_default_local_addr() { let client = Client::builder() .server_addr("localhost:7835") .token("secret") .build() .expect("should use default local_addr"); assert_eq!(client.config().local_addr, "127.0.0.1:8080"); } #[test] fn test_client_not_running_initially() { let client = Client::builder() .server_addr("localhost:7835") .token("secret") .build() .expect("should build"); assert!(!client.is_running()); } #[test] fn test_client_builder_startup_timeout_none_is_stored() { let client = Client::builder() .server_addr("localhost:7835") .token("secret") .build() .expect("should build"); assert_eq!(client.config().startup_timeout, None); } } ``` -------------------------------- ### into_ok Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Returns the contained Ok value, but never panics. This is a nightly-only experimental API. ```APIDOC ## into_ok ### Description Returns the contained `Ok` value, but never panics. Unlike `unwrap`, this method is known to never panic on the result types it is implemented for. Therefore, it can be used instead of `unwrap` as a maintainability safeguard that will fail to compile if the error type of the `Result` is later changed to an error that can actually occur. ### Signature pub const fn into_ok(self) -> T where E: Into, ### Examples ``` fn only_good_news() -> Result { Ok("this is fine".into()) } let s: String = only_good_news().into_ok(); println!("{s}"); ``` ``` -------------------------------- ### iter: iterate over Ok value Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Use to iterate over the possibly contained value, yielding one item if `Ok` and none if `Err`. The example shows both cases. ```rust let x: Result = Ok(7); assert_eq!(x.iter().next(), Some(&7)); let x: Result = Err("nothing!"); assert_eq!(x.iter().next(), None); ``` -------------------------------- ### ClientBuilder::build() Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/client.rs.html Builds the client after validating configuration. Returns an error if required configuration is missing (server_addr, token) or if TLS validation failed. ```rust pub fn build(self) -> Result { self.config.validate()?; if let Some(error) = self.tls_validation_error { return Err(error); } Ok(Client { config: self.config, transport_config: self.transport_config.unwrap_or_default(), shutdown_tx: None, task: None, }) } ``` -------------------------------- ### ClientConfig::validate Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/config/struct.ClientConfig.html Validates the configuration. Returns Ok(()) if valid, otherwise an error. ```APIDOC ## ClientConfig::validate ### Description Validates the configuration. ### Method validate ### Signature pub fn validate(&self) -> Result<()> ### Parameters None ### Returns Result<()> - Ok(()) if valid, otherwise an error. ``` -------------------------------- ### ClientBuilder Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/client/struct.ClientBuilder.html Builder for constructing a `Client` with ergonomic configuration. Provides methods to set server address, token, local address, tunnel ID, reconnection settings, timeouts, limits, TLS, and build the client. ```APIDOC ## ClientBuilder ### Description Builder for constructing a `Client` with ergonomic configuration. ### Methods #### `server_addr(self, addr: impl Into) -> Self` Set the server address to connect to. Format: `host:port` (e.g., `"tunnel.example.com:7835"`). #### `token(self, token: impl Into) -> Self` Set the authentication token. #### `local_addr(self, addr: impl Into) -> Self` Set the local address to forward traffic to. Default: `"127.0.0.1:8080"`. #### `tunnel_id(self, id: impl Into) -> Self` Set the tunnel ID used for HTTP routing (matched against the Host header). If not set, the server assigns a random UUID as the tunnel ID. #### `auto_reconnect(self, enabled: bool) -> Self` Enable or disable automatic reconnection. Default: `true`. #### `reconnect_delay(self, delay: Duration) -> Self` Set the delay between reconnection attempts. Default: 5 seconds. #### `startup_timeout(self, timeout: Option) -> Self` Set the maximum time to wait for the initial connection in `Client::start`. Pass `None` to wait indefinitely. Default: 30 seconds. #### `limits(self, limits: &LimitsConfig) -> Self` Configure resource limits (frame size, session, and stream bounds). Defaults to `LimitsConfig::default`. A `max_frame_bytes` of `0` or a value larger than `u32::MAX` is rejected by `ClientBuilder::build`. #### `tls(self, config: &TlsConfig) -> Self` Configure TLS for the connection. When enabled, the client will use TLS to connect to the server. #### `build(self) -> Result` Build the client with the configured options. ##### Errors Returns an error if required configuration is missing: - `server_addr` must be set - `token` must be set ``` -------------------------------- ### Collect Result from checked_sub with underflow Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Collects Results from a checked subtraction, returning the first Err when underflow occurs. The example shows that the collection stops at the first Err and returns that error. ```rust let v = vec![1, 2, 0]; let res: Result, &'static str> = v.iter().map(|x: &u32| x.checked_sub(1).ok_or("Underflow!") ).collect(); assert_eq!(res, Err("Underflow!")); ``` -------------------------------- ### Server::builder() Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Creates a new server builder for configuring an embeddable tunnel server. ```APIDOC ## Server::builder() ### Description Creates a new `ServerBuilder` instance for configuring and constructing an embeddable tunnel server. ### Method `builder()` ### Signature `pub fn builder() -> ServerBuilder` ### Parameters None. ### Returns - `ServerBuilder` - A builder for configuring the server. ### Example ```rust let server = Server::builder() .bind("0.0.0.0:7835".parse().expect("valid tunnel bind")) .token("secret") .build() .expect("valid server configuration"); ``` ``` -------------------------------- ### Configure per-session rate limits Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Configures per-session rate limits (stream-open and byte rates). Defaults to RateLimitConfig::default. Every value must be non-zero, and bytes_per_sec must fit in a u32. ```rust pub fn rate_limits(mut self, limits: &RateLimitConfig) -> Self { self.config.rate_limits = limits.clone(); self } ``` -------------------------------- ### and_then: chain fallible operations Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Shows how `and_then` chains fallible operations, returning the first `Err` encountered. The example assumes `sq_then_to_string` is a function that returns a `Result`. ```rust assert_eq!(Ok(2).and_then(sq_then_to_string), Ok(4.to_string())); assert_eq!(Ok(1_000_000).and_then(sq_then_to_string), Err("overflowed")); assert_eq!(Err("not a number").and_then(sq_then_to_string), Err("not a number")); ``` -------------------------------- ### Test server builder TLS missing key Source: https://docs.rs/ferrotunnel/latest/src/ferrotunnel/server.rs.html Verifies that TLS configuration with a certificate but no key path is rejected. The error message must contain "key_path". ```rust let tls = TlsConfig { enabled: true, cert_path: Some(PathBuf::from("server.crt")), ..Default::default() }; let result = Server::builder().token("secret").tls(&tls).build(); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("key_path")); ``` -------------------------------- ### Result::and_then with checked_mul Source: https://docs.rs/ferrotunnel/latest/ferrotunnel/type.Result.html Shows `and_then` usage for control flow based on `Result` values. The example defines a function that squares a number and converts it to a string, returning an error on overflow. ```rust fn sq_then_to_string(x: u32) -> Result { x.checked_mul(x).map(|sq| sq.to_string()).ok_or("overflowed") } ```