using System.Text;
using System.Text.Json;
using RoslynBridge.WebApi.Models;
namespace RoslynBridge.WebApi.Services;
///
/// HTTP client for communicating with the Roslyn Bridge Visual Studio plugin
///
public class RoslynBridgeClient : IRoslynBridgeClient
{
private readonly HttpClient _httpClient;
private readonly IInstanceRegistryService _registryService;
private readonly ILogger _logger;
private readonly JsonSerializerOptions _jsonOptions;
public RoslynBridgeClient(
HttpClient httpClient,
IInstanceRegistryService registryService,
ILogger logger)
{
_httpClient = httpClient;
_registryService = registryService;
_logger = logger;
_jsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
}
public async Task ExecuteQueryAsync(
RoslynQueryRequest request,
int? instancePort = null,
CancellationToken cancellationToken = default)
{
try
{
var targetPort = await ResolveInstancePortAsync(instancePort, request);
if (targetPort == null)
{
return new RoslynQueryResponse
{
Success = false,
Error = "No Visual Studio instance available"
};
}
_logger.LogInformation("Executing query: {QueryType} on port {Port}", request.QueryType, targetPort);
var json = JsonSerializer.Serialize(request, _jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"http://localhost:{targetPort}/query";
var response = await _httpClient.PostAsync(url, content, cancellationToken);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync(cancellationToken);
_logger.LogError("Query failed with status {StatusCode}: {Error}", response.StatusCode, errorContent);
return new RoslynQueryResponse
{
Success = false,
Error = $"Request failed with status {response.StatusCode}: {errorContent}"
};
}
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
var result = JsonSerializer.Deserialize(responseContent, _jsonOptions);
if (result == null)
{
return new RoslynQueryResponse
{
Success = false,
Error = "Failed to deserialize response"
};
}
_logger.LogInformation("Query executed successfully: {Success}", result.Success);
return result;
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "HTTP request failed while executing query");
return new RoslynQueryResponse
{
Success = false,
Error = $"Failed to connect to Roslyn Bridge server: {ex.Message}"
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error while executing query");
return new RoslynQueryResponse
{
Success = false,
Error = $"Unexpected error: {ex.Message}"
};
}
}
public async Task IsHealthyAsync(int? instancePort = null, CancellationToken cancellationToken = default)
{
try
{
var targetPort = await ResolveInstancePortAsync(instancePort, null);
if (targetPort == null)
{
return false;
}
var request = new RoslynQueryRequest { QueryType = "health" };
var json = JsonSerializer.Serialize(request, _jsonOptions);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"http://localhost:{targetPort}/health";
var response = await _httpClient.PostAsync(url, content, cancellationToken);
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Health check failed");
return false;
}
}
///
/// Resolves which VS instance port to use based on provided hints
///
private Task ResolveInstancePortAsync(int? explicitPort, RoslynQueryRequest? request)
{
// If explicit port specified, use it
if (explicitPort.HasValue)
{
return Task.FromResult(explicitPort.Value);
}
// Try to find instance by solution path from request
if (request != null && !string.IsNullOrEmpty(request.FilePath))
{
// Extract solution path by looking for .sln file in the path hierarchy
var directory = Path.GetDirectoryName(request.FilePath);
while (!string.IsNullOrEmpty(directory))
{
var solutionFiles = Directory.GetFiles(directory, "*.sln");
if (solutionFiles.Length > 0)
{
var instance = _registryService.GetBySolutionPath(solutionFiles[0]);
if (instance != null)
{
_logger.LogDebug("Found instance by solution path: {SolutionPath}", solutionFiles[0]);
return Task.FromResult(instance.Port);
}
}
directory = Path.GetDirectoryName(directory);
}
}
// Fall back to first available instance
var instances = _registryService.GetAllInstances().ToList();
if (instances.Any())
{
_logger.LogDebug("Using first available instance: port {Port}", instances[0].Port);
return Task.FromResult(instances[0].Port);
}
_logger.LogWarning("No Visual Studio instances registered");
return Task.FromResult(null);
}
}