dynami 0.1.0

Automatic Axum router generation from directory structure with file-system based routing
Documentation
use anyhow::Result;
use std::fs;
use std::path::Path;
use syn::{Item, parse_file};

pub fn detect_appstate(root_mod_path: &Path) -> Result<Option<String>> {
    if !root_mod_path.exists() {
        return Ok(None);
    }

    let content = fs::read_to_string(root_mod_path)?;
    let parsed = parse_file(&content)?;

    // Look for structs with "State" in the name or that have Clone derive
    for item in parsed.items {
        if let Item::Struct(item_struct) = item {
            let name = item_struct.ident.to_string();

            // Check if name contains "State"
            if name.contains("State") {
                return Ok(Some(name));
            }

            // Check for Clone derive as a hint (common for state structs)
            for attr in &item_struct.attrs {
                if attr.path().is_ident("derive") {
                    if let Ok(meta) = attr.parse_args::<syn::Meta>() {
                        let meta_str = quote::quote!(#meta).to_string();
                        if meta_str.contains("Clone") && is_likely_state_name(&name) {
                            return Ok(Some(name));
                        }
                    }
                }
            }
        }
        // Also check for type aliases
        else if let Item::Type(item_type) = item {
            let name = item_type.ident.to_string();
            if name.contains("State") {
                return Ok(Some(name));
            }
        }
    }

    Ok(None)
}

fn is_likely_state_name(name: &str) -> bool {
    // Check if name seems like it could be a state struct
    // Common patterns: AppContext, Context, App, SharedState, etc.
    let lowercase = name.to_lowercase();
    lowercase.contains("context")|| lowercase.contains("app") || lowercase.contains("shared")
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    #[test]
    fn test_detect_appstate() -> Result<()> {
        let temp = TempDir::new()?;
        let mod_path = temp.path().join("mod.rs");

        let content = r#"
            #[derive(Clone)]
            pub struct AppState {
                pub db: Database,
            }
        "#;

        fs::write(&mod_path, content)?;

        let result = detect_appstate(&mod_path)?;
        assert_eq!(result, Some("AppState".to_string()));

        Ok(())
    }

    #[test]
    fn test_detect_no_state() -> Result<()> {
        let temp = TempDir::new()?;
        let mod_path = temp.path().join("mod.rs");

        let content = r#"
            pub struct User {
                pub name: String,
            }
        "#;

        fs::write(&mod_path, content)?;

        let result = detect_appstate(&mod_path)?;
        assert_eq!(result, None);

        Ok(())
    }

    #[test]
    fn test_detect_custom_state_name() -> Result<()> {
        let temp = TempDir::new()?;
        let mod_path = temp.path().join("mod.rs");

        let content = r#"
            #[derive(Clone)]
            pub struct MyAppState {
                pub config: Config,
            }
        "#;

        fs::write(&mod_path, content)?;

        let result = detect_appstate(&mod_path)?;
        assert_eq!(result, Some("MyAppState".to_string()));

        Ok(())
    }
}