nargo-server 0.0.0

Nargo development server
Documentation
//! Nargo development server.
//!
//! This module provides development server, static file serving, and mock server functionality.

#![warn(missing_docs)]

use nargo_types::Result;
use std::{
    collections::HashMap,
    path::{Path, PathBuf},
    sync::Arc,
};

use nargo_types::NargoContext;
use serde::{Deserialize, Serialize};

/// Server mode.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ServerMode {
    /// Development mode with hot reload.
    Dev,
    /// Static file serving mode.
    Serve,
    /// Production mode.
    Prod,
}

/// Server options.
#[derive(Debug, Clone)]
pub struct ServerOptions {
    /// Server mode.
    pub mode: ServerMode,
    /// Root directory.
    pub root: PathBuf,
    /// Port to listen on.
    pub port: u16,
    /// Host to bind to.
    pub host: String,
}

impl Default for ServerOptions {
    fn default() -> Self {
        Self { mode: ServerMode::Dev, root: PathBuf::from("."), port: 3000, host: "127.0.0.1".to_string() }
    }
}

/// Development server.
pub struct DevServer {
    options: ServerOptions,
}

impl DevServer {
    /// Create a new dev server.
    pub fn new(options: ServerOptions) -> Self {
        Self { options }
    }

    /// Start the server.
    pub async fn start(&self) -> Result<()> {
        println!("Dev server running on {}:{}...", self.options.host, self.options.port);
        Ok(())
    }
}

/// Run the server with the given options.
pub async fn run(_ctx: Arc<NargoContext>, options: ServerOptions) -> Result<()> {
    let server = DevServer::new(options);
    server.start().await
}

/// Mock route definition.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MockRoute {
    /// Route path pattern.
    pub path: String,
    /// HTTP method (GET, POST, etc.).
    #[serde(default = "default_method")]
    pub method: String,
    /// Response status code.
    #[serde(default = "default_status")]
    pub status: u16,
    /// Response headers.
    #[serde(default)]
    pub headers: HashMap<String, String>,
    /// Response body.
    #[serde(default)]
    pub body: String,
    /// Response body file path (alternative to body).
    #[serde(default)]
    pub body_file: Option<PathBuf>,
    /// Delay in milliseconds before responding.
    #[serde(default)]
    pub delay_ms: u64,
}

fn default_method() -> String {
    "GET".to_string()
}

fn default_status() -> u16 {
    200
}

/// Mock server for development.
pub struct MockServer {
    _ctx: Arc<NargoContext>,
    routes: Vec<MockRoute>,
}

impl MockServer {
    /// Create a new mock server.
    pub fn new(ctx: Arc<NargoContext>) -> Self {
        Self { _ctx: ctx, routes: Vec::new() }
    }

    /// Add a route to the mock server.
    pub fn add_route(&mut self, route: MockRoute) {
        self.routes.push(route);
    }

    /// Load routes from a directory.
    pub fn load_from_dir(&mut self, dir: &Path) -> Result<()> {
        for entry in std::fs::read_dir(dir).map_err(|e| nargo_types::Error::external_error("server".to_string(), e.to_string(), nargo_types::Span::unknown()))? {
            let entry = entry.map_err(|e| nargo_types::Error::external_error("server".to_string(), e.to_string(), nargo_types::Span::unknown()))?;
            let path = entry.path();
            if path.extension().map_or(false, |ext| ext == "json") {
                let content = std::fs::read_to_string(&path).map_err(|e| nargo_types::Error::external_error("server".to_string(), e.to_string(), nargo_types::Span::unknown()))?;
                let routes: Vec<MockRoute> = serde_json::from_str(&content).map_err(|e| nargo_types::Error::external_error("server".to_string(), e.to_string(), nargo_types::Span::unknown()))?;
                self.routes.extend(routes);
            }
        }
        Ok(())
    }

    /// Get the number of routes.
    pub fn routes_count(&self) -> usize {
        self.routes.len()
    }

    /// Start the mock server.
    pub async fn start(&self, port: u16) -> Result<()> {
        println!("Mock server running on port {}...", port);
        println!("Loaded {} routes", self.routes.len());
        Ok(())
    }
}