rext_core/
lib.rs

1//! # rext_core
2//!
3//! The `rext_core` crate is the library that powers Rext, the fullstack, batteries included Rust framework for developing web applications.
4//!
5//! It handles the absolute most basic requirements nearly all web apps will share, such as routing, API documentation, and the front-end.
6//!
7//! Status: 0%
8//!
9//! [Visit Rext](https://rextstack.org)
10//!
11
12use axum::{Router, routing::get};
13use std::net::SocketAddr;
14use tokio::net::TcpListener;
15
16/// Custom error codes for RextCore
17#[derive(thiserror::Error, Debug)]
18pub enum RextCoreError {
19    #[error("Failed to bind to address {address}: {source}")]
20    BindError {
21        address: SocketAddr,
22        source: std::io::Error,
23    },
24    #[error("Server failed to start: {0}")]
25    ServerStart(#[from] std::io::Error),
26}
27
28/// Configuration for the server
29pub struct ServerConfig {
30    pub host: [u8; 4],
31    pub port: u16,
32}
33
34impl Default for ServerConfig {
35    fn default() -> Self {
36        Self {
37            host: [0, 0, 0, 0],
38            port: 3000,
39        }
40    }
41}
42
43/// Create and configure the router with all routes
44pub fn create_router() -> Router {
45    Router::new().route("/", get(root))
46}
47
48/// Start the server with the given configuration
49/// Traditional blocking behavior
50pub async fn server_blocking(config: ServerConfig) -> Result<(), RextCoreError> {
51    let app = create_router();
52    let address = SocketAddr::from((config.host, config.port));
53
54    let listener = TcpListener::bind(address)
55        .await
56        .map_err(|e| RextCoreError::BindError { address, source: e })?;
57
58    axum::serve(listener, app).await?;
59    Ok(())
60}
61
62/// Start the server and return the bound address and a handle
63/// This is non-blocking
64pub async fn server_non_blocking(
65    config: ServerConfig,
66) -> Result<
67    (
68        SocketAddr,
69        tokio::task::JoinHandle<Result<(), RextCoreError>>,
70    ),
71    RextCoreError,
72> {
73    let app = create_router();
74    let address = SocketAddr::from((config.host, config.port));
75
76    let listener = TcpListener::bind(address)
77        .await
78        .map_err(|e| RextCoreError::BindError { address, source: e })?;
79
80    // Get the actual bound address (useful when port is 0 for dynamic allocation)
81    let bound_addr = listener.local_addr().map_err(RextCoreError::ServerStart)?;
82
83    // Spawn the server in a separate task
84    let server_handle = tokio::spawn(async move {
85        axum::serve(listener, app)
86            .await
87            .map_err(RextCoreError::ServerStart)
88    });
89
90    Ok((bound_addr, server_handle))
91}
92
93async fn root() -> &'static str {
94    "Hello, World!"
95}