Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions DevProxy.Abstractions/Plugins/BasePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ public abstract class BasePlugin<TConfiguration>(
BasePlugin(logger, urlsToWatch), IPlugin<TConfiguration> where TConfiguration : new()
{
private TConfiguration? _configuration;
private readonly HttpClient _httpClient = httpClient;

protected HttpClient PluginHttpClient { get; } = httpClient;

protected IProxyConfiguration ProxyConfiguration { get; } = proxyConfiguration;
public TConfiguration Configuration
Expand Down Expand Up @@ -144,7 +145,7 @@ public override async Task InitializeAsync(InitArgs e, CancellationToken cancell
}

ProxyUtils.ValidateSchemaVersion(schemaUrl, Logger);
return await ProxyUtils.ValidateJsonAsync(configSection.GetRawText(), schemaUrl, _httpClient, Logger, cancellationToken);
return await ProxyUtils.ValidateJsonAsync(configSection.GetRawText(), schemaUrl, PluginHttpClient, Logger, cancellationToken);
}
catch (Exception ex)
{
Expand Down
95 changes: 52 additions & 43 deletions DevProxy.Abstractions/Utils/MSGraphDbUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@

namespace DevProxy.Abstractions.Utils;

public static class MSGraphDbUtils
public sealed class MSGraphDbUtils(HttpClient httpClient, ILogger<MSGraphDbUtils> logger) : IDisposable
{
private static readonly Dictionary<string, OpenApiDocument> _openApiDocuments = [];
private static readonly string[] graphVersions = ["v1.0", "beta"];

private static SqliteConnection? _msGraphDbConnection;
private readonly Dictionary<string, OpenApiDocument> _openApiDocuments = [];
private readonly HttpClient _httpClient = httpClient;
private readonly ILogger<MSGraphDbUtils> _logger = logger;
private SqliteConnection? _msGraphDbConnection;

// v1 refers to v1 of the db schema, not the graph version
public static string MSGraphDbFilePath => Path.Combine(ProxyUtils.AppFolder!, "msgraph-openapi-v1.db");

public static SqliteConnection MSGraphDbConnection
public SqliteConnection MSGraphDbConnection
{
get
{
Expand All @@ -33,12 +34,12 @@ public static SqliteConnection MSGraphDbConnection
}
}

public static async Task<int> GenerateMSGraphDbAsync(ILogger logger, bool skipIfUpdatedToday, CancellationToken cancellationToken)
public async Task<int> GenerateMSGraphDbAsync(bool skipIfUpdatedToday, CancellationToken cancellationToken)
{
var appFolder = ProxyUtils.AppFolder;
if (string.IsNullOrEmpty(appFolder))
{
logger.LogError("App folder {AppFolder} not found", appFolder);
_logger.LogError("App folder {AppFolder} not found", appFolder);
return 1;
}

Expand All @@ -48,61 +49,64 @@ public static async Task<int> GenerateMSGraphDbAsync(ILogger logger, bool skipIf
var modifiedToday = dbFileInfo.Exists && dbFileInfo.LastWriteTime.Date == DateTime.Now.Date;
if (modifiedToday && skipIfUpdatedToday)
{
logger.LogInformation("Microsoft Graph database already updated today");
_logger.LogInformation("Microsoft Graph database already updated today");
return 1;
}

await UpdateOpenAPIGraphFilesIfNecessaryAsync(appFolder, logger, cancellationToken);
await LoadOpenAPIFilesAsync(appFolder, logger, cancellationToken);
await UpdateOpenAPIGraphFilesIfNecessaryAsync(appFolder, cancellationToken);
await LoadOpenAPIFilesAsync(appFolder, cancellationToken);
if (_openApiDocuments.Count < 1)
{
logger.LogDebug("No OpenAPI files found or couldn't load them");
_logger.LogDebug("No OpenAPI files found or couldn't load them");
return 1;
}

var dbConnection = MSGraphDbConnection;
await CreateDbAsync(dbConnection, logger, cancellationToken);
await FillDataAsync(dbConnection, logger, cancellationToken);
await CreateDbAsync(cancellationToken);
await FillDataAsync(cancellationToken);

logger.LogInformation("Microsoft Graph database successfully updated");
_logger.LogInformation("Microsoft Graph database successfully updated");

return 0;
}
catch (Exception ex)
{
logger.LogError(ex, "Error generating Microsoft Graph database");
_logger.LogError(ex, "Error generating Microsoft Graph database");
return 1;
}

}

private static string GetGraphOpenApiYamlFileName(string version) => $"graph-{version.Replace(".", "_", StringComparison.OrdinalIgnoreCase)}-openapi.yaml";

private static async Task CreateDbAsync(SqliteConnection dbConnection, ILogger logger, CancellationToken cancellationToken)
private async Task CreateDbAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Creating database...");
_logger.LogInformation("Creating database...");

var dbConnection = MSGraphDbConnection;

logger.LogDebug("Dropping endpoints table...");
_logger.LogDebug("Dropping endpoints table...");
var dropTable = dbConnection.CreateCommand();
dropTable.CommandText = "DROP TABLE IF EXISTS endpoints";
_ = await dropTable.ExecuteNonQueryAsync(cancellationToken);

logger.LogDebug("Creating endpoints table...");
_logger.LogDebug("Creating endpoints table...");
var createTable = dbConnection.CreateCommand();
// when you change the schema, increase the db version number in ProxyUtils
createTable.CommandText = "CREATE TABLE IF NOT EXISTS endpoints (path TEXT, graphVersion TEXT, hasSelect BOOLEAN)";
_ = await createTable.ExecuteNonQueryAsync(cancellationToken);

logger.LogDebug("Creating index on endpoints and version...");
_logger.LogDebug("Creating index on endpoints and version...");
// Add an index on the path and graphVersion columns
var createIndex = dbConnection.CreateCommand();
createIndex.CommandText = "CREATE INDEX IF NOT EXISTS idx_endpoints_path_version ON endpoints (path, graphVersion)";
_ = await createIndex.ExecuteNonQueryAsync(cancellationToken);
}

private static async Task FillDataAsync(SqliteConnection dbConnection, ILogger logger, CancellationToken cancellationToken)
private async Task FillDataAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Filling database...");
_logger.LogInformation("Filling database...");

var dbConnection = MSGraphDbConnection;

var i = 0;

Expand All @@ -113,7 +117,7 @@ private static async Task FillDataAsync(SqliteConnection dbConnection, ILogger l
var graphVersion = openApiDocument.Key;
var document = openApiDocument.Value;

logger.LogDebug("Filling database for {GraphVersion}...", graphVersion);
_logger.LogDebug("Filling database for {GraphVersion}...", graphVersion);

var insertEndpoint = dbConnection.CreateCommand();
insertEndpoint.CommandText = "INSERT INTO endpoints (path, graphVersion, hasSelect) VALUES (@path, @graphVersion, @hasSelect)";
Expand All @@ -125,20 +129,20 @@ private static async Task FillDataAsync(SqliteConnection dbConnection, ILogger l
{
cancellationToken.ThrowIfCancellationRequested();

logger.LogTrace("Endpoint {GraphVersion}{Key}...", graphVersion, path.Key);
_logger.LogTrace("Endpoint {GraphVersion}{Key}...", graphVersion, path.Key);

// Get the GET operation for this path
var getOperation = path.Value.Operations.FirstOrDefault(o => o.Key == OperationType.Get).Value;
if (getOperation == null)
{
logger.LogTrace("No GET operation found for {GraphVersion}{Key}", graphVersion, path.Key);
_logger.LogTrace("No GET operation found for {GraphVersion}{Key}", graphVersion, path.Key);
continue;
}

// Check if the GET operation has a $select parameter
var hasSelect = getOperation.Parameters.Any(p => p.Name == "$select");

logger.LogTrace("Inserting endpoint {GraphVersion}{Key} with hasSelect={HasSelect}...", graphVersion, path.Key, hasSelect);
_logger.LogTrace("Inserting endpoint {GraphVersion}{Key} with hasSelect={HasSelect}...", graphVersion, path.Key, hasSelect);
insertEndpoint.Parameters["@path"].Value = path.Key;
insertEndpoint.Parameters["@graphVersion"].Value = graphVersion;
insertEndpoint.Parameters["@hasSelect"].Value = hasSelect;
Expand All @@ -147,54 +151,53 @@ private static async Task FillDataAsync(SqliteConnection dbConnection, ILogger l
}
}

logger.LogInformation("Inserted {EndpointCount} endpoints in the database", i);
_logger.LogInformation("Inserted {EndpointCount} endpoints in the database", i);
}

private static async Task UpdateOpenAPIGraphFilesIfNecessaryAsync(string folder, ILogger logger, CancellationToken cancellationToken)
private async Task UpdateOpenAPIGraphFilesIfNecessaryAsync(string folder, CancellationToken cancellationToken)
{
logger.LogInformation("Checking for updated OpenAPI files...");
_logger.LogInformation("Checking for updated OpenAPI files...");

foreach (var version in graphVersions)
{
try
{
var file = new FileInfo(Path.Combine(folder, GetGraphOpenApiYamlFileName(version)));
logger.LogDebug("Checking for updated OpenAPI file {File}...", file);
_logger.LogDebug("Checking for updated OpenAPI file {File}...", file);
if (file.Exists && file.LastWriteTime.Date == DateTime.Now.Date)
{
logger.LogInformation("File {File} already updated today", file);
_logger.LogInformation("File {File} already updated today", file);
continue;
}

var url = $"https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/master/openapi/{version}/openapi.yaml";
logger.LogInformation("Downloading OpenAPI file from {Url}...", url);
_logger.LogInformation("Downloading OpenAPI file from {Url}...", url);

using var client = new HttpClient();
var response = await client.GetStringAsync(url, cancellationToken);
var response = await _httpClient.GetStringAsync(url, cancellationToken);
await File.WriteAllTextAsync(file.FullName, response, cancellationToken);

logger.LogDebug("Downloaded OpenAPI file from {Url} to {File}", url, file);
_logger.LogDebug("Downloaded OpenAPI file from {Url} to {File}", url, file);
}
catch (Exception ex)
{
logger.LogError(ex, "Error updating OpenAPI files");
_logger.LogError(ex, "Error updating OpenAPI files");
}
}
}

private static async Task LoadOpenAPIFilesAsync(string folder, ILogger logger, CancellationToken cancellationToken)
private async Task LoadOpenAPIFilesAsync(string folder, CancellationToken cancellationToken)
{
logger.LogInformation("Loading OpenAPI files...");
_logger.LogInformation("Loading OpenAPI files...");

foreach (var version in graphVersions)
{
var filePath = Path.Combine(folder, GetGraphOpenApiYamlFileName(version));
var file = new FileInfo(filePath);
logger.LogDebug("Loading OpenAPI file for {FilePath}...", filePath);
_logger.LogDebug("Loading OpenAPI file for {FilePath}...", filePath);

if (!file.Exists)
{
logger.LogDebug("File {FilePath} does not exist", filePath);
_logger.LogDebug("File {FilePath} does not exist", filePath);
continue;
}

Expand All @@ -203,12 +206,18 @@ private static async Task LoadOpenAPIFilesAsync(string folder, ILogger logger, C
var openApiDocument = await new OpenApiStreamReader().ReadAsync(file.OpenRead(), cancellationToken);
_openApiDocuments[version] = openApiDocument.OpenApiDocument;

logger.LogDebug("Added OpenAPI file {FilePath} for {Version}", filePath, version);
_logger.LogDebug("Added OpenAPI file {FilePath} for {Version}", filePath, version);
}
catch (Exception ex)
{
logger.LogError(ex, "Error loading OpenAPI file {FilePath}", filePath);
_logger.LogError(ex, "Error loading OpenAPI file {FilePath}", filePath);
}
}
}

public void Dispose()
{
_msGraphDbConnection?.Dispose();
_httpClient?.Dispose();
}
}
9 changes: 6 additions & 3 deletions DevProxy.Plugins/Guidance/GraphSelectGuidancePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@ namespace DevProxy.Plugins.Guidance;

public sealed class GraphSelectGuidancePlugin(
ILogger<GraphSelectGuidancePlugin> logger,
ISet<UrlToWatch> urlsToWatch) : BasePlugin(logger, urlsToWatch)
ISet<UrlToWatch> urlsToWatch,
MSGraphDbUtils msGraphDbUtils) : BasePlugin(logger, urlsToWatch)
{
private readonly MSGraphDbUtils _msGraphDbUtils = msGraphDbUtils;

public override string Name => nameof(GraphSelectGuidancePlugin);

public override async Task InitializeAsync(InitArgs e, CancellationToken cancellationToken)
{
await base.InitializeAsync(e, cancellationToken);

// let's not await so that it doesn't block the proxy startup
_ = MSGraphDbUtils.GenerateMSGraphDbAsync(Logger, true, cancellationToken);
_ = _msGraphDbUtils.GenerateMSGraphDbAsync(true, cancellationToken);
}

public override Task AfterResponseAsync(ProxyResponseArgs e, CancellationToken cancellationToken)
Expand Down Expand Up @@ -82,7 +85,7 @@ private bool EndpointSupportsSelect(string graphVersion, string relativeUrl)

try
{
var dbConnection = MSGraphDbUtils.MSGraphDbConnection;
var dbConnection = _msGraphDbUtils.MSGraphDbConnection;
// lookup information from the database
var selectEndpoint = dbConnection.CreateCommand();
selectEndpoint.CommandText = "SELECT hasSelect FROM endpoints WHERE path = @path AND graphVersion = @graphVersion";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,10 @@ private async Task EvaluateMinimalScopesAsync(
try
{
var url = $"https://devxapi-func-prod-eastus.azurewebsites.net/permissions?scopeType={GraphUtils.GetScopeTypeString(scopeType)}";
using var client = new HttpClient();
var stringPayload = JsonSerializer.Serialize(payload, ProxyUtils.JsonSerializerOptions);
Logger.LogDebug("Calling {Url} with payload{NewLine}{Payload}", url, Environment.NewLine, stringPayload);

var response = await client.PostAsJsonAsync(url, payload, cancellationToken);
var response = await PluginHttpClient.PostAsJsonAsync(url, payload, cancellationToken);
var content = await response.Content.ReadAsStringAsync(cancellationToken);

Logger.LogDebug("Response:{NewLine}{Content}", Environment.NewLine, content);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,10 @@ public override async Task AfterRecordingStopAsync(RecordingArgs e, Cancellation
try
{
var url = $"https://devxapi-func-prod-eastus.azurewebsites.net/permissions?scopeType={GraphUtils.GetScopeTypeString(Configuration.Type)}";
using var client = new HttpClient();
var stringPayload = JsonSerializer.Serialize(payload, ProxyUtils.JsonSerializerOptions);
Logger.LogDebug("Calling {Url} with payload\r\n{StringPayload}", url, stringPayload);

var response = await client.PostAsJsonAsync(url, payload, cancellationToken);
var response = await PluginHttpClient.PostAsJsonAsync(url, payload, cancellationToken);
var content = await response.Content.ReadAsStringAsync(cancellationToken);

Logger.LogDebug("Response:\r\n{Content}", content);
Expand Down
10 changes: 5 additions & 5 deletions DevProxy/Announcement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@

namespace DevProxy;

static class Announcement
internal sealed class Announcement(HttpClient httpClient)
{
private static readonly Uri announcementUrl = new("https://aka.ms/devproxy/announcement");
private readonly HttpClient _httpClient = httpClient;

public static async Task ShowAsync()
public async Task ShowAsync()
{
var announcement = await GetAsync();
if (!string.IsNullOrEmpty(announcement))
Expand All @@ -22,12 +23,11 @@ public static async Task ShowAsync()
}
}

public static async Task<string?> GetAsync()
private async Task<string?> GetAsync()
{
try
{
using var client = new HttpClient();
return await client.GetStringAsync(announcementUrl);
return await _httpClient.GetStringAsync(announcementUrl);
}
catch
{
Expand Down
6 changes: 4 additions & 2 deletions DevProxy/Commands/MsGraphDbCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ namespace DevProxy.Commands;
sealed class MsGraphDbCommand : Command
{
private readonly ILogger _logger;
private readonly MSGraphDbUtils _msGraphDbUtils;

public MsGraphDbCommand(ILogger<MsGraphDbCommand> logger) :
public MsGraphDbCommand(ILogger<MsGraphDbCommand> logger, MSGraphDbUtils msGraphDbUtils) :
base("msgraphdb", "Generate a local SQLite database with Microsoft Graph API metadata")
{
_logger = logger;
_msGraphDbUtils = msGraphDbUtils;
ConfigureCommand();
}

Expand All @@ -25,6 +27,6 @@ private void ConfigureCommand()

private async Task GenerateMsGraphDbAsync(ParseResult parseResult, CancellationToken cancellationToken)
{
_ = await MSGraphDbUtils.GenerateMSGraphDbAsync(_logger, false, cancellationToken);
_ = await _msGraphDbUtils.GenerateMSGraphDbAsync(false, cancellationToken);
}
}
3 changes: 3 additions & 0 deletions DevProxy/Extensions/IServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using DevProxy;
using DevProxy.Abstractions.LanguageModel;
using DevProxy.Abstractions.Proxy;
using DevProxy.Abstractions.Utils;
using DevProxy.Commands;
using DevProxy.Proxy;

Expand Down Expand Up @@ -45,6 +46,8 @@ static IServiceCollection AddApplicationServices(
.AddSingleton<UpdateNotification>()
.AddSingleton<ProxyEngine>()
.AddSingleton<DevProxyCommand>()
.AddSingleton<Announcement>()
.AddSingleton<MSGraphDbUtils>()
.AddHttpClient();

_ = services.AddPlugins(configuration, options);
Expand Down
Loading