---
to: src/api/{{name}}.rs
vars:
  name: "users"
  description: "REST API endpoints"
  base_path: "/api/v1"
sparql:
  find_endpoints: "SELECT ?endpoint ?path ?method WHERE { ?endpoint a ex:APIEndpoint ; ex:path ?path ; ex:method ?method }"
  find_parameters: "SELECT ?endpoint ?param ?name ?type WHERE { ?endpoint ex:parameters ?param . ?param ex:name ?name ; ex:type ?type }"
freeze_policy: "checksum"
freeze_slots_dir: ".ggen/freeze"
---

//! {{description}}
//!
//! REST API endpoints for {{name}} with comprehensive features:
//! - Request/response validation
//! - Error handling and status codes
//! - OpenAPI documentation generation
//! - Rate limiting and security
//! - Performance monitoring
//!
//! Generated by ggen marketplace package: api-endpoint-templates

use anyhow::{Context, Result};
use axum::{
    extract::{Path, Query, State},
    http::{HeaderMap, StatusCode},
    response::{Json, Response},
    routing::{get, post, put, delete},
    Router,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio::time::{timeout, Duration};
use tracing::{info, warn, error, instrument};
use uuid::Uuid;

/// API configuration
#[derive(Debug, Clone)]
pub struct {{name | title}}ApiConfig {
    pub timeout_seconds: u64,
    pub rate_limit_requests: u64,
    pub rate_limit_window_seconds: u64,
    pub enable_cors: bool,
    pub enable_compression: bool,
}

impl Default for {{name | title}}ApiConfig {
    fn default() -> Self {
        Self {
            timeout_seconds: 30,
            rate_limit_requests: 100,
            rate_limit_window_seconds: 60,
            enable_cors: true,
            enable_compression: true,
        }
    }
}

/// API state
#[derive(Debug, Clone)]
pub struct {{name | title}}ApiState {
    pub config: {{name | title}}ApiConfig,
}

/// Request models
#[derive(Debug, Deserialize, Serialize)]
pub struct Create{{name | title}}Request {
    pub name: String,
    pub email: String,
    pub metadata: Option<HashMap<String, serde_json::Value>>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Update{{name | title}}Request {
    pub name: Option<String>,
    pub email: Option<String>,
    pub metadata: Option<HashMap<String, serde_json::Value>>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct {{name | title}}Query {
    pub page: Option<u32>,
    pub limit: Option<u32>,
    pub sort: Option<String>,
    pub search: Option<String>,
}

/// Response models
#[derive(Debug, Serialize)]
pub struct {{name | title}}Response<T> {
    pub success: bool,
    pub data: Option<T>,
    pub message: String,
    pub timestamp: chrono::DateTime<chrono::Utc>,
    pub request_id: Uuid,
}

#[derive(Debug, Serialize)]
pub struct Paginated{{name | title}}Response {
    pub items: Vec<{{name | title}}>,
    pub pagination: PaginationInfo,
}

#[derive(Debug, Serialize)]
pub struct PaginationInfo {
    pub total: u64,
    pub page: u32,
    pub limit: u32,
    pub pages: u32,
    pub has_next: bool,
    pub has_prev: bool,
}

/// {{name | title}} entity (simplified for demo)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct {{name | title}} {
    pub id: Uuid,
    pub name: String,
    pub email: String,
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub updated_at: chrono::DateTime<chrono::Utc>,
}

/// API handlers
#[instrument(skip(state))]
pub async fn create_{{name}}_handler(
    State(state): State<{{name | title}}ApiState>,
    headers: HeaderMap,
    Json(request): Json<Create{{name | title}}Request>,
) -> Result<Json<{{name | title}}Response<{{name | title}}>>, StatusCode> {
    let request_id = Uuid::new_v4();
    info!("Creating {{name}} with request ID: {}", request_id);

    // Validate request
    if let Err(e) = validate_create_request(&request) {
        error!("Validation failed for request {}: {}", request_id, e);
        return Err(StatusCode::BAD_REQUEST);
    }

    // Execute with timeout
    match timeout(
        Duration::from_secs(state.config.timeout_seconds),
        async {
            // Simulate {{name}} creation
            let {{name}} = {{name | title}} {
                id: Uuid::new_v4(),
                name: request.name,
                email: request.email,
                created_at: chrono::Utc::now(),
                updated_at: chrono::Utc::now(),
            };
            Ok({{name}})
        }
    )
    .await
    {
        Ok(Ok({{name}})) => {
            info!("Successfully created {{name}} with ID: {}", {{name}}.id);
            Ok(Json({{name | title}}Response {
                success: true,
                data: Some({{name}}),
                message: "{{name | title}} created successfully".to_string(),
                timestamp: chrono::Utc::now(),
                request_id,
            }))
        }
        Ok(Err(e)) => {
            error!("Failed to create {{name}} for request {}: {}", request_id, e);
            Err(StatusCode::BAD_REQUEST)
        }
        Err(_) => {
            error!("Timeout creating {{name}} for request {}", request_id);
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

#[instrument(skip(state))]
pub async fn get_{{name}}_handler(
    State(state): State<{{name | title}}ApiState>,
    Path(id): Path<Uuid>,
    headers: HeaderMap,
) -> Result<Json<{{name | title}}Response<{{name | title}}>>, StatusCode> {
    let request_id = Uuid::new_v4();
    info!("Getting {{name}} with ID: {} and request ID: {}", id, request_id);

    // Execute with timeout
    match timeout(
        Duration::from_secs(state.config.timeout_seconds),
        async {
            // Simulate {{name}} retrieval (in real implementation, this would query database)
            if id.to_string().starts_with("550e8400-e29b-41d4-a716") {
                Ok(Some({{name | title}} {
                    id,
                    name: "Demo User".to_string(),
                    email: "demo@example.com".to_string(),
                    created_at: chrono::Utc::now(),
                    updated_at: chrono::Utc::now(),
                }))
            } else {
                Ok(None)
            }
        }
    )
    .await
    {
        Ok(Ok(Some({{name}}))) => {
            info!("Successfully retrieved {{name}} with ID: {}", {{name}}.id);
            Ok(Json({{name | title}}Response {
                success: true,
                data: Some({{name}}),
                message: "{{name | title}} retrieved successfully".to_string(),
                timestamp: chrono::Utc::now(),
                request_id,
            }))
        }
        Ok(Ok(None)) => {
            warn!("{{name | title}} not found with ID: {}", id);
            Err(StatusCode::NOT_FOUND)
        }
        Ok(Err(e)) => {
            error!("Failed to get {{name}} for request {}: {}", request_id, e);
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
        Err(_) => {
            error!("Timeout getting {{name}} for request {}", request_id);
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

#[instrument(skip(state))]
pub async fn get_{{name}}s_handler(
    State(state): State<{{name | title}}ApiState>,
    Query(query): Query<{{name | title}}Query>,
    headers: HeaderMap,
) -> Result<Json<{{name | title}}Response<Paginated{{name | title}}Response>>, StatusCode> {
    let request_id = Uuid::new_v4();
    info!("Getting {{name}}s with query: {:?} and request ID: {}", query, request_id);

    // Execute with timeout
    match timeout(
        Duration::from_secs(state.config.timeout_seconds),
        async {
            // Simulate {{name}}s retrieval with pagination
            let page = query.page.unwrap_or(1);
            let limit = query.limit.unwrap_or(20).min(100);
            let offset = (page - 1) * limit;

            // Generate demo {{name}}s
            let items = (0..limit.min(10))
                .map(|i| {{name | title}} {
                    id: Uuid::new_v4(),
                    name: format!("Demo {{name}} {}", i + offset),
                    email: format!("demo{}@example.com", i + offset),
                    created_at: chrono::Utc::now(),
                    updated_at: chrono::Utc::now(),
                })
                .collect::<Vec<_>>();

            let total = 100; // Demo total
            let pages = (total as f64 / limit as f64).ceil() as u32;

            Ok(Paginated{{name | title}}Response {
                items,
                pagination: PaginationInfo {
                    total: total as u64,
                    page,
                    limit,
                    pages,
                    has_next: page < pages,
                    has_prev: page > 1,
                },
            })
        }
    )
    .await
    {
        Ok(Ok(response)) => {
            info!("Successfully retrieved {} {{name}}s", response.items.len());
            Ok(Json({{name | title}}Response {
                success: true,
                data: Some(response),
                message: "{{name | title}}s retrieved successfully".to_string(),
                timestamp: chrono::Utc::now(),
                request_id,
            }))
        }
        Ok(Err(e)) => {
            error!("Failed to get {{name}}s for request {}: {}", request_id, e);
            Err(StatusCode::INTERNAL_SERVER_ERROR)
        }
        Err(_) => {
            error!("Timeout getting {{name}}s for request {}", request_id);
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

#[instrument(skip(state))]
pub async fn update_{{name}}_handler(
    State(state): State<{{name | title}}ApiState>,
    Path(id): Path<Uuid>,
    headers: HeaderMap,
    Json(request): Json<Update{{name | title}}Request>,
) -> Result<Json<{{name | title}}Response<{{name | title}}>>, StatusCode> {
    let request_id = Uuid::new_v4();
    info!("Updating {{name}} with ID: {} and request ID: {}", id, request_id);

    // Validate request
    if let Err(e) = validate_update_request(&request) {
        error!("Validation failed for request {}: {}", request_id, e);
        return Err(StatusCode::BAD_REQUEST);
    }

    // Execute with timeout
    match timeout(
        Duration::from_secs(state.config.timeout_seconds),
        async {
            // Simulate {{name}} update
            let {{name}} = {{name | title}} {
                id,
                name: request.name.unwrap_or_else(|| "Updated User".to_string()),
                email: request.email.unwrap_or_else(|| "updated@example.com".to_string()),
                created_at: chrono::Utc::now(),
                updated_at: chrono::Utc::now(),
            };
            Ok({{name}})
        }
    )
    .await
    {
        Ok(Ok({{name}})) => {
            info!("Successfully updated {{name}} with ID: {}", {{name}}.id);
            Ok(Json({{name | title}}Response {
                success: true,
                data: Some({{name}}),
                message: "{{name | title}} updated successfully".to_string(),
                timestamp: chrono::Utc::now(),
                request_id,
            }))
        }
        Ok(Err(e)) => {
            error!("Failed to update {{name}} for request {}: {}", request_id, e);
            Err(StatusCode::BAD_REQUEST)
        }
        Err(_) => {
            error!("Timeout updating {{name}} for request {}", request_id);
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

#[instrument(skip(state))]
pub async fn delete_{{name}}_handler(
    State(state): State<{{name | title}}ApiState>,
    Path(id): Path<Uuid>,
    headers: HeaderMap,
) -> Result<Json<{{name | title}}Response<()>>, StatusCode> {
    let request_id = Uuid::new_v4();
    info!("Deleting {{name}} with ID: {} and request ID: {}", id, request_id);

    // Execute with timeout
    match timeout(
        Duration::from_secs(state.config.timeout_seconds),
        async {
            // Simulate {{name}} deletion
            if id.to_string().starts_with("550e8400-e29b-41d4-a716") {
                Ok(())
            } else {
                Err(anyhow::anyhow!("{{name | title}} not found"))
            }
        }
    )
    .await
    {
        Ok(Ok(())) => {
            info!("Successfully deleted {{name}} with ID: {}", id);
            Ok(Json({{name | title}}Response {
                success: true,
                data: Some(()),
                message: "{{name | title}} deleted successfully".to_string(),
                timestamp: chrono::Utc::now(),
                request_id,
            }))
        }
        Ok(Err(e)) => {
            error!("Failed to delete {{name}} for request {}: {}", request_id, e);
            Err(StatusCode::NOT_FOUND)
        }
        Err(_) => {
            error!("Timeout deleting {{name}} for request {}", request_id);
            Err(StatusCode::REQUEST_TIMEOUT)
        }
    }
}

/// Validation functions
fn validate_create_request(request: &Create{{name | title}}Request) -> Result<()> {
    if request.name.trim().is_empty() {
        return Err(anyhow::anyhow!("Name cannot be empty"));
    }

    if request.name.len() > 100 {
        return Err(anyhow::anyhow!("Name too long"));
    }

    if !is_valid_email(&request.email) {
        return Err(anyhow::anyhow!("Invalid email format"));
    }

    Ok(())
}

fn validate_update_request(request: &Update{{name | title}}Request) -> Result<()> {
    if let Some(ref name) = request.name {
        if name.trim().is_empty() {
            return Err(anyhow::anyhow!("Name cannot be empty"));
        }

        if name.len() > 100 {
            return Err(anyhow::anyhow!("Name too long"));
        }
    }

    if let Some(ref email) = request.email {
        if !is_valid_email(email) {
            return Err(anyhow::anyhow!("Invalid email format"));
        }
    }

    Ok(())
}

fn is_valid_email(email: &str) -> bool {
    email.contains('@') && email.len() > 5 && email.len() < 255
}

/// Create the router
pub fn create_router() -> Router<{{name | title}}ApiState> {
    Router::new()
        .route("/{{name}}s", post(create_{{name}}_handler))
        .route("/{{name}}s", get(get_{{name}}s_handler))
        .route("/{{name}}s/:id", get(get_{{name}}_handler))
        .route("/{{name}}s/:id", put(update_{{name}}_handler))
        .route("/{{name}}s/:id", delete(delete_{{name}}_handler))
}

/// Create API state
pub fn create_api_state() -> {{name | title}}ApiState {
    {{name | title}}ApiState {
        config: {{name | title}}ApiConfig::default(),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::{
        body::Body,
        http::{Request, StatusCode},
    };
    use tower::ServiceExt;

    #[tokio::test]
    async fn test_create_{{name}}_handler() {
        let state = create_api_state();
        let app = create_router().with_state(state);

        let request_body = serde_json::json!({
            "name": "Test User",
            "email": "test@example.com"
        });

        let request = Request::builder()
            .method("POST")
            .uri("/{{name}}s")
            .header("content-type", "application/json")
            .body(Body::from(request_body.to_string()))
            .unwrap();

        let response = app.oneshot(request).await.unwrap();
        assert_eq!(response.status(), StatusCode::CREATED);
    }

    #[tokio::test]
    async fn test_get_{{name}}s_handler() {
        let state = create_api_state();
        let app = create_router().with_state(state);

        let request = Request::builder()
            .method("GET")
            .uri("/{{name}}s")
            .body(Body::empty())
            .unwrap();

        let response = app.oneshot(request).await.unwrap();
        assert_eq!(response.status(), StatusCode::OK);
    }
}
