avalonia-mcp-tools 0.1.0

MCP tools for AvaloniaUI development assistance
Documentation
//! Service Layer tool - Service patterns and implementation
use avalonia_mcp_core::error::AvaloniaMcpError;
use avalonia_mcp_core::markdown::MarkdownOutputBuilder;
use rmcp::model::{CallToolResult, Content};
use rmcp::tool;
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct ServiceLayerParams {
    pub service_type: Option<String>,
    pub include_examples: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DomainServiceParams {
    pub domain_name: String,
    pub include_validation: Option<bool>,
    pub include_events: Option<bool>,
}

#[derive(Debug, Clone, Default)]
pub struct ServiceLayerTool;

impl ServiceLayerTool {
    pub fn new() -> Self { Self }

    #[tool(description = "Generate service layer patterns for AvaloniaUI applications. Covers service interfaces, dependency injection, and business logic organization.")]
    pub async fn generate_service_layer(
        &self,
        params: ServiceLayerParams,
    ) -> Result<CallToolResult, AvaloniaMcpError> {
        let include_examples = params.include_examples.unwrap_or(true);
        let service_type = params.service_type.as_deref().unwrap_or("general");

        let output = match service_type {
            "repository" => self.generate_repository_pattern(include_examples),
            "unitofwork" => self.generate_unit_of_work(include_examples),
            "mediator" => self.generate_mediat_r_pattern(include_examples),
            _ => self.generate_general_services(include_examples),
        };

        Ok(CallToolResult::success(vec![Content::text(output)]))
    }

    fn generate_general_services(&self, include_examples: bool) -> String {
        let mut builder = MarkdownOutputBuilder::new()
            .heading(1, "Service Layer Patterns")
            .paragraph("Service layer implementation for AvaloniaUI applications.")
            .heading(2, "Service Interface")
            .code_block("csharp", r#"public interface IDataService
{
    Task<IEnumerable<Item>> GetAllAsync(CancellationToken ct = default);
    Task<Item?> GetByIdAsync(int id, CancellationToken ct = default);
    Task<Item> CreateAsync(Item item, CancellationToken ct = default);
    Task UpdateAsync(Item item, CancellationToken ct = default);
    Task DeleteAsync(int id, CancellationToken ct = default);
}

public class DataService : IDataService
{
    private readonly IRepository _repository;
    private readonly IMapper _mapper;
    
    public DataService(IRepository repository, IMapper mapper)
    {
        _repository = repository;
        _mapper = mapper;
    }
    
    public async Task<IEnumerable<Item>> GetAllAsync(CancellationToken ct = default)
    {
        var entities = await _repository.GetAllAsync(ct);
        return _mapper.Map<IEnumerable<Item>>(entities);
    }
    
    // Implement other methods...
}"#);

        if include_examples {
            builder = builder
                .heading(2, "Service Registration")
                .code_block("csharp", r#"// Register services in DI
services.AddScoped<IDataService, DataService>();
services.AddScoped<IRepository, Repository>();
services.AddSingleton<IMapper, Mapper>();

// Use in ViewModel
public class MainViewModel
{
    public MainViewModel(IDataService dataService) { }
}"#);
        }

        builder.heading(2, "Best Practices")
            .task_list(vec![(true, "Define interfaces for services"), (true, "Inject dependencies via constructor"), (true, "Use async/await"), (true, "Handle exceptions at service level"), (false, "Implement caching where appropriate")])
            .build()
    }

    fn generate_repository_pattern(&self, _include_examples: bool) -> String {
        MarkdownOutputBuilder::new()
            .heading(1, "Repository Pattern")
            .paragraph("Generic repository pattern for data access abstraction.")
            .heading(2, "Generic Repository")
            .code_block("csharp", r#"public interface IRepository<T> where T : class
{
    Task<IEnumerable<T>> GetAllAsync();
    Task<T?> GetByIdAsync(int id);
    Task<T> AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(int id);
}

public class Repository<T> : IRepository<T> where T : class
{
    protected readonly DbContext _context;
    protected readonly DbSet<T> _dbSet;
    
    public Repository(DbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }
    
    public async Task<IEnumerable<T>> GetAllAsync() =>
        await _dbSet.ToListAsync();
    
    public async Task<T?> GetByIdAsync(int id) =>
        await _dbSet.FindAsync(id);
    
    public async Task<T> AddAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
        await _context.SaveChangesAsync();
        return entity;
    }
    
    // Implement Update and Delete...
}"#)
            .build()
    }

    fn generate_unit_of_work(&self, _include_examples: bool) -> String {
        MarkdownOutputBuilder::new()
            .heading(1, "Unit of Work Pattern")
            .paragraph("Unit of Work for coordinating multiple repositories.")
            .heading(2, "IUnitOfWork Interface")
            .code_block("csharp", r#"public interface IUnitOfWork : IDisposable
{
    IRepository<User> Users { get; }
    IRepository<Order> Orders { get; }
    Task<int> SaveChangesAsync(CancellationToken ct = default);
    Task BeginTransactionAsync();
    Task CommitTransactionAsync();
    Task RollbackTransactionAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;
    private IRepository<User>? _users;
    private IRepository<Order>? _orders;
    
    public IRepository<User> Users => 
        _users ??= new Repository<User>(_context);
    
    public IRepository<Order> Orders =>
        _orders ??= new Repository<Order>(_context);
    
    public async Task<int> SaveChangesAsync(CancellationToken ct = default) =>
        await _context.SaveChangesAsync(ct);
}"#)
            .build()
    }

    fn generate_mediat_r_pattern(&self, _include_examples: bool) -> String {
        MarkdownOutputBuilder::new()
            .heading(1, "Mediator Pattern with MediatR")
            .paragraph("CQRS and mediator pattern for decoupled architecture.")
            .heading(2, "Commands and Queries")
            .code_block("csharp", r#"// Command
public class CreateUserCommand : IRequest<int>
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class CreateUserHandler : IRequestHandler<CreateUserCommand, int>
{
    private readonly IRepository<User> _repo;

    public CreateUserHandler(IRepository<User> repo) => _repo = repo;

    public async Task<int> Handle(CreateUserCommand request, CancellationToken ct)
    {
        var user = new User { Name = request.Name, Email = request.Email };
        await _repo.AddAsync(user);
        return user.Id;
    }
}

// Query
public class GetUserQuery : IRequest<User>
{
    public int Id { get; set; }
}

// Usage in ViewModel
public class UserViewModel
{
    private readonly IMediator _mediator;

    public UserViewModel(IMediator mediator) => _mediator = mediator;

    public async Task<int> CreateUserAsync(string name, string email)
    {
        return await _mediator.Send(new CreateUserCommand
        {
            Name = name,
            Email = email
        });
    }
}"#)
            .build()
    }

    #[tool(description = "Creates domain service patterns for complex business logic in AvaloniaUI applications")]
    pub async fn generate_domain_service(&self, params: DomainServiceParams) -> Result<CallToolResult, AvaloniaMcpError> {
        if params.domain_name.is_empty() {
            return Err(AvaloniaMcpError::validation("Domain name cannot be empty"));
        }
        let include_validation = params.include_validation.unwrap_or(true);
        let include_events = params.include_events.unwrap_or(true);
        let validation = if include_validation {
            "\n    public void Validate(Order order)\n    {\n        if (order == null) throw new ArgumentNullException(nameof(order));\n        if (order.TotalAmount < 0) throw new ArgumentException(\"Amount cannot be negative\");\n    }"
        } else { "" };
        let events = if include_events {
            "\n\n    public event EventHandler<OrderCreatedEventArgs> OrderCreated;\n    protected virtual void OnOrderCreated(Order order) =>\n        OrderCreated?.Invoke(this, new OrderCreatedEventArgs(order));"
        } else { "" };
        let d = &params.domain_name;
        let builder = MarkdownOutputBuilder::new()
            .heading(1, &format!("Domain Service: {}", d))
            .heading(2, "Configuration").task_list(vec![(true, format!("Domain: {}", d)), (true, format!("Validation: {}", include_validation)), (true, format!("Events: {}", include_events))])
            .heading(2, "Interface").code_block("csharp", &format!("public interface I{d}Service\n{{\n    Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation);\n}}"))
            .heading(2, "Implementation").code_block("csharp", &format!("public class {d}Service : I{d}Service\n{{\n    private readonly IRepository _repository;\n    private readonly ILogger _logger;{validation}{events}\n\n    public {d}Service(IRepository repository, ILogger<{d}Service> logger)\n    {{\n        _repository = repository;\n        _logger = logger;\n    }}\n\n    public async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> op)\n    {{\n        _logger.LogInformation(\"Starting {{Domain}} operation\", \"{d}\");\n        try {{\n            var result = await op();\n            return result;\n        }}\n        catch (Exception ex) {{\n            _logger.LogError(ex, \"Error in {{Domain}} operation\", \"{d}\");\n            throw;\n        }}\n    }}\n}}"))
            .heading(2, "DDD Principles").list(&["Aggregate roots control access", "Value objects are immutable", "Domain events capture state changes", "Services orchestrate complex operations"]);
        Ok(CallToolResult::success(vec![Content::text(builder.build())]))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[tokio::test]
    async fn test_generate_service_layer() {
        let tool = ServiceLayerTool::new();
        let result = tool.generate_service_layer(ServiceLayerParams { service_type: None, include_examples: Some(true) }).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
    }

    #[tokio::test]
    async fn test_generate_domain_service_success() {
        let tool = ServiceLayerTool::new();
        let params = DomainServiceParams {
            domain_name: "Order".to_string(),
            include_validation: Some(true),
            include_events: Some(true),
        };
        let result = tool.generate_domain_service(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
    }

    #[tokio::test]
    async fn test_generate_domain_service_empty_name() {
        let tool = ServiceLayerTool::new();
        let params = DomainServiceParams {
            domain_name: "".to_string(),
            include_validation: None,
            include_events: None,
        };
        let result = tool.generate_domain_service(params).await;
        assert!(result.is_err());
    }
}