use crate::entities::Entity;
#[derive(Debug, Clone)]
pub struct ApiEndpoint {
pub method: String,
pub path: String,
pub handler_name: String,
pub entity_name: String,
}
pub struct ApiGenerator {
pub prefix: String,
}
impl ApiGenerator {
pub fn new(prefix: String) -> Self {
Self { prefix }
}
pub fn generate_endpoints(&self, entity: &Entity) -> Vec<ApiEndpoint> {
let entity_name = entity.name().to_lowercase();
let mut endpoints = Vec::new();
endpoints.push(ApiEndpoint {
method: "GET".to_string(),
path: format!("{}/{}", self.prefix, entity_name),
handler_name: format!("list_{}", entity_name),
entity_name: entity.name().to_string(),
});
endpoints.push(ApiEndpoint {
method: "GET".to_string(),
path: format!("{}/{}/{{id}}", self.prefix, entity_name),
handler_name: format!("get_{}", entity_name),
entity_name: entity.name().to_string(),
});
endpoints.push(ApiEndpoint {
method: "POST".to_string(),
path: format!("{}/{}", self.prefix, entity_name),
handler_name: format!("create_{}", entity_name),
entity_name: entity.name().to_string(),
});
endpoints.push(ApiEndpoint {
method: "PUT".to_string(),
path: format!("{}/{}/{{id}}", self.prefix, entity_name),
handler_name: format!("update_{}", entity_name),
entity_name: entity.name().to_string(),
});
endpoints.push(ApiEndpoint {
method: "PATCH".to_string(),
path: format!("{}/{}/{{id}}", self.prefix, entity_name),
handler_name: format!("patch_{}", entity_name),
entity_name: entity.name().to_string(),
});
endpoints.push(ApiEndpoint {
method: "DELETE".to_string(),
path: format!("{}/{}/{{id}}", self.prefix, entity_name),
handler_name: format!("delete_{}", entity_name),
entity_name: entity.name().to_string(),
});
for fk in &entity.schema.foreign_keys {
let relationship_name = fk.field.trim_end_matches("_id");
endpoints.push(ApiEndpoint {
method: "GET".to_string(),
path: format!("{}/{}/{{id}}/{}", self.prefix, entity_name, relationship_name),
handler_name: format!(
"get_{}_by_{}",
fk.target_entity.to_lowercase(),
relationship_name
),
entity_name: entity.name().to_string(),
});
}
endpoints
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::entities::Entity;
use crate::schema::VbrSchemaDefinition;
use mockforge_data::SchemaDefinition;
fn create_test_entity(name: &str) -> Entity {
let base_schema = SchemaDefinition::new(name.to_string());
let vbr_schema = VbrSchemaDefinition::new(base_schema);
Entity::new(name.to_string(), vbr_schema)
}
fn create_entity_with_foreign_key(name: &str) -> Entity {
let base_schema = SchemaDefinition::new(name.to_string());
let mut vbr_schema = VbrSchemaDefinition::new(base_schema);
vbr_schema.foreign_keys.push(crate::schema::ForeignKeyDefinition {
field: "user_id".to_string(),
target_entity: "User".to_string(),
target_field: "id".to_string(),
on_delete: crate::schema::CascadeAction::Cascade,
on_update: crate::schema::CascadeAction::NoAction,
});
Entity::new(name.to_string(), vbr_schema)
}
#[test]
fn test_api_generator_new() {
let generator = ApiGenerator::new("/api".to_string());
assert_eq!(generator.prefix, "/api");
}
#[test]
fn test_api_generator_custom_prefix() {
let generator = ApiGenerator::new("/v2/api".to_string());
assert_eq!(generator.prefix, "/v2/api");
}
#[test]
fn test_generate_endpoints_count() {
let generator = ApiGenerator::new("/api".to_string());
let entity = create_test_entity("User");
let endpoints = generator.generate_endpoints(&entity);
assert_eq!(endpoints.len(), 6);
}
#[test]
fn test_generate_endpoints_methods() {
let generator = ApiGenerator::new("/api".to_string());
let entity = create_test_entity("User");
let endpoints = generator.generate_endpoints(&entity);
let methods: Vec<&str> = endpoints.iter().map(|e| e.method.as_str()).collect();
assert!(methods.contains(&"GET"));
assert!(methods.contains(&"POST"));
assert!(methods.contains(&"PUT"));
assert!(methods.contains(&"PATCH"));
assert!(methods.contains(&"DELETE"));
}
#[test]
fn test_generate_endpoints_paths() {
let generator = ApiGenerator::new("/api".to_string());
let entity = create_test_entity("User");
let endpoints = generator.generate_endpoints(&entity);
let paths: Vec<&str> = endpoints.iter().map(|e| e.path.as_str()).collect();
assert!(paths.contains(&"/api/user"));
assert!(paths.contains(&"/api/user/{id}"));
}
#[test]
fn test_generate_endpoints_handler_names() {
let generator = ApiGenerator::new("/api".to_string());
let entity = create_test_entity("User");
let endpoints = generator.generate_endpoints(&entity);
let handlers: Vec<&str> = endpoints.iter().map(|e| e.handler_name.as_str()).collect();
assert!(handlers.contains(&"list_user"));
assert!(handlers.contains(&"get_user"));
assert!(handlers.contains(&"create_user"));
assert!(handlers.contains(&"update_user"));
assert!(handlers.contains(&"patch_user"));
assert!(handlers.contains(&"delete_user"));
}
#[test]
fn test_generate_endpoints_entity_name() {
let generator = ApiGenerator::new("/api".to_string());
let entity = create_test_entity("Product");
let endpoints = generator.generate_endpoints(&entity);
for endpoint in endpoints {
assert_eq!(endpoint.entity_name, "Product");
}
}
#[test]
fn test_generate_endpoints_with_foreign_key() {
let generator = ApiGenerator::new("/api".to_string());
let entity = create_entity_with_foreign_key("Order");
let endpoints = generator.generate_endpoints(&entity);
assert_eq!(endpoints.len(), 7);
let relationship_endpoint = endpoints.iter().find(|e| e.path.contains("/user"));
assert!(relationship_endpoint.is_some());
assert_eq!(relationship_endpoint.unwrap().method, "GET");
}
#[test]
fn test_api_endpoint_debug() {
let endpoint = ApiEndpoint {
method: "GET".to_string(),
path: "/api/test".to_string(),
handler_name: "test_handler".to_string(),
entity_name: "Test".to_string(),
};
let debug = format!("{:?}", endpoint);
assert!(debug.contains("ApiEndpoint"));
assert!(debug.contains("GET"));
assert!(debug.contains("/api/test"));
}
#[test]
fn test_api_endpoint_clone() {
let endpoint = ApiEndpoint {
method: "POST".to_string(),
path: "/api/test".to_string(),
handler_name: "create_test".to_string(),
entity_name: "Test".to_string(),
};
let cloned = endpoint.clone();
assert_eq!(endpoint.method, cloned.method);
assert_eq!(endpoint.path, cloned.path);
assert_eq!(endpoint.handler_name, cloned.handler_name);
assert_eq!(endpoint.entity_name, cloned.entity_name);
}
#[test]
fn test_lowercase_entity_in_path() {
let generator = ApiGenerator::new("/api".to_string());
let entity = create_test_entity("UserProfile");
let endpoints = generator.generate_endpoints(&entity);
let list_endpoint = endpoints.iter().find(|e| e.handler_name == "list_userprofile");
assert!(list_endpoint.is_some());
assert_eq!(list_endpoint.unwrap().path, "/api/userprofile");
}
}