Skip to content

Commit 554bb9e

Browse files
Adds UrlDiscoveryPlugin and preset (#969)
1 parent bae96bd commit 554bb9e

File tree

6 files changed

+236
-66
lines changed

6 files changed

+236
-66
lines changed
Lines changed: 99 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,100 @@
1-
// Copyright (c) Microsoft Corporation.
2-
// Licensed under the MIT License.
3-
4-
using System.Text.Json;
5-
using Microsoft.DevProxy.Abstractions;
6-
using Microsoft.DevProxy.Plugins.RequestLogs;
7-
using Microsoft.Extensions.Configuration;
8-
using Microsoft.Extensions.Logging;
9-
10-
namespace Microsoft.DevProxy.Plugins.Reporters;
11-
12-
public class JsonReporter(IPluginEvents pluginEvents, IProxyContext context, ILogger logger, ISet<UrlToWatch> urlsToWatch, IConfigurationSection? configSection = null) : BaseReporter(pluginEvents, context, logger, urlsToWatch, configSection)
13-
{
14-
public override string Name => nameof(JsonReporter);
15-
public override string FileExtension => ".json";
16-
17-
private readonly Dictionary<Type, Func<object, object>> _transformers = new()
18-
{
19-
{ typeof(ExecutionSummaryPluginReportByUrl), TransformExecutionSummary },
20-
{ typeof(ExecutionSummaryPluginReportByMessageType), TransformExecutionSummary },
21-
};
22-
23-
protected override string GetReport(KeyValuePair<string, object> report)
24-
{
25-
Logger.LogDebug("Serializing report {reportKey}...", report.Key);
26-
27-
var reportData = report.Value;
28-
var reportType = reportData.GetType();
29-
30-
if (_transformers.TryGetValue(reportType, out var transform))
31-
{
32-
Logger.LogDebug("Transforming {reportType} using {transform}...", reportType.Name, transform.Method.Name);
33-
reportData = transform(reportData);
34-
}
35-
else
36-
{
37-
Logger.LogDebug("No transformer found for {reportType}", reportType.Name);
38-
}
39-
40-
if (reportData is string strVal)
41-
{
42-
Logger.LogDebug("{reportKey} is a string. Checking if it's JSON...", report.Key);
43-
44-
try
45-
{
46-
JsonSerializer.Deserialize<object>(strVal);
47-
Logger.LogDebug("{reportKey} is already JSON, ignore", report.Key);
48-
// already JSON, ignore
49-
return strVal;
50-
}
51-
catch
52-
{
53-
Logger.LogDebug("{reportKey} is not JSON, serializing...", report.Key);
54-
}
55-
}
56-
57-
return JsonSerializer.Serialize(reportData, ProxyUtils.JsonSerializerOptions);
58-
}
59-
60-
private static object TransformExecutionSummary(object report)
61-
{
62-
var executionSummaryReport = (ExecutionSummaryPluginReportBase)report;
63-
return executionSummaryReport.Data;
64-
}
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text;
5+
using System.Text.Json;
6+
using Microsoft.DevProxy.Abstractions;
7+
using Microsoft.DevProxy.Plugins.RequestLogs;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace Microsoft.DevProxy.Plugins.Reporters;
12+
13+
public class JsonReporter(IPluginEvents pluginEvents, IProxyContext context, ILogger logger, ISet<UrlToWatch> urlsToWatch, IConfigurationSection? configSection = null) : BaseReporter(pluginEvents, context, logger, urlsToWatch, configSection)
14+
{
15+
public override string Name => nameof(JsonReporter);
16+
private string _fileExtension = ".json";
17+
public override string FileExtension => _fileExtension;
18+
19+
private readonly Dictionary<Type, Func<object, object>> _transformers = new()
20+
{
21+
{ typeof(ExecutionSummaryPluginReportByUrl), TransformExecutionSummary },
22+
{ typeof(ExecutionSummaryPluginReportByMessageType), TransformExecutionSummary },
23+
{ typeof(UrlDiscoveryPluginReport), TransformUrlDiscoveryReport }
24+
};
25+
26+
protected override string GetReport(KeyValuePair<string, object> report)
27+
{
28+
Logger.LogDebug("Serializing report {reportKey}...", report.Key);
29+
30+
var reportData = report.Value;
31+
var reportType = reportData.GetType();
32+
_fileExtension = reportType.Name == nameof(UrlDiscoveryPluginReport) ? ".jsonc" : ".json";
33+
34+
if (_transformers.TryGetValue(reportType, out var transform))
35+
{
36+
Logger.LogDebug("Transforming {reportType} using {transform}...", reportType.Name, transform.Method.Name);
37+
reportData = transform(reportData);
38+
}
39+
else
40+
{
41+
Logger.LogDebug("No transformer found for {reportType}", reportType.Name);
42+
}
43+
44+
if (reportData is string strVal)
45+
{
46+
Logger.LogDebug("{reportKey} is a string. Checking if it's JSON...", report.Key);
47+
48+
try
49+
{
50+
JsonSerializer.Deserialize<object>(strVal, ProxyUtils.JsonSerializerOptions);
51+
Logger.LogDebug("{reportKey} is already JSON, ignore", report.Key);
52+
// already JSON, ignore
53+
return strVal;
54+
}
55+
catch
56+
{
57+
Logger.LogDebug("{reportKey} is not JSON, serializing...", report.Key);
58+
}
59+
}
60+
61+
return JsonSerializer.Serialize(reportData, ProxyUtils.JsonSerializerOptions);
62+
}
63+
64+
private static object TransformExecutionSummary(object report)
65+
{
66+
var executionSummaryReport = (ExecutionSummaryPluginReportBase)report;
67+
return executionSummaryReport.Data;
68+
}
69+
70+
private static object TransformUrlDiscoveryReport(object report)
71+
{
72+
var urlDiscoveryPluginReport = (UrlDiscoveryPluginReport)report;
73+
74+
var sb = new StringBuilder();
75+
sb.AppendLine("{");
76+
sb.AppendLine(" // Wildcards");
77+
sb.AppendLine(" // ");
78+
sb.AppendLine(" // You can use wildcards to catch multiple URLs with the same pattern.");
79+
sb.AppendLine(" // For example, you can use the following URL pattern to catch all API requests to");
80+
sb.AppendLine(" // JSON Placeholder API:");
81+
sb.AppendLine(" // ");
82+
sb.AppendLine(" // https://jsonplaceholder.typicode.com/*");
83+
sb.AppendLine(" // ");
84+
sb.AppendLine(" // Excluding URLs");
85+
sb.AppendLine(" // ");
86+
sb.AppendLine(" // You can exclude URLs with ! to prevent them from being intercepted.");
87+
sb.AppendLine(" // For example, you can exclude the URL https://jsonplaceholder.typicode.com/authors");
88+
sb.AppendLine(" // by using the following URL pattern:");
89+
sb.AppendLine(" // ");
90+
sb.AppendLine(" // !https://jsonplaceholder.typicode.com/authors");
91+
sb.AppendLine(" // https://jsonplaceholder.typicode.com/*");
92+
sb.AppendLine(" \"urlsToWatch\": [");
93+
sb.AppendJoin($",{Environment.NewLine}", urlDiscoveryPluginReport.Data.Select(u => $" \"{u}\""));
94+
sb.AppendLine("");
95+
sb.AppendLine(" ]");
96+
sb.AppendLine("}");
97+
98+
return sb.ToString();
99+
}
65100
}

dev-proxy-plugins/Reporters/MarkdownReporter.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public class MarkdownReporter(IPluginEvents pluginEvents, IProxyContext context,
2424
{ typeof(HttpFileGeneratorPlugin), TransformHttpFileGeneratorReport },
2525
{ typeof(GraphMinimalPermissionsGuidancePluginReport), TransformMinimalPermissionsGuidanceReport },
2626
{ typeof(GraphMinimalPermissionsPluginReport), TransformMinimalPermissionsReport },
27-
{ typeof(OpenApiSpecGeneratorPluginReport), TransformOpenApiSpecGeneratorReport }
27+
{ typeof(OpenApiSpecGeneratorPluginReport), TransformOpenApiSpecGeneratorReport },
28+
{ typeof(UrlDiscoveryPluginReport), TransformUrlDiscoveryReport }
2829
};
2930

3031
private const string _requestsInterceptedMessage = "Requests intercepted";
@@ -483,6 +484,43 @@ void transformPermissionsInfo(GraphMinimalPermissionsInfo permissionsInfo, strin
483484
return sb.ToString();
484485
}
485486

487+
private static string? TransformUrlDiscoveryReport(object report)
488+
{
489+
var urlDiscoveryPluginReport = (UrlDiscoveryPluginReport)report;
490+
491+
var sb = new StringBuilder();
492+
sb.AppendLine("## Wildcards");
493+
sb.AppendLine("");
494+
sb.AppendLine("You can use wildcards to catch multiple URLs with the same pattern.");
495+
sb.AppendLine("For example, you can use the following URL pattern to catch all API requests to");
496+
sb.AppendLine("JSON Placeholder API:");
497+
sb.AppendLine("");
498+
sb.AppendLine("```text");
499+
sb.AppendLine("https://jsonplaceholder.typicode.com/*");
500+
sb.AppendLine("```");
501+
sb.AppendLine("");
502+
sb.AppendLine("## Excluding URLs");
503+
sb.AppendLine("");
504+
sb.AppendLine("You can exclude URLs with ! to prevent them from being intercepted.");
505+
sb.AppendLine("For example, you can exclude the URL `https://jsonplaceholder.typicode.com/authors`");
506+
sb.AppendLine("by using the following URL pattern:");
507+
sb.AppendLine("");
508+
sb.AppendLine("```text");
509+
sb.AppendLine("!https://jsonplaceholder.typicode.com/authors");
510+
sb.AppendLine("https://jsonplaceholder.typicode.com/*");
511+
sb.AppendLine("```");
512+
sb.AppendLine("");
513+
sb.AppendLine("Intercepted URLs:");
514+
sb.AppendLine();
515+
sb.AppendLine("```text");
516+
517+
sb.AppendJoin(Environment.NewLine, urlDiscoveryPluginReport.Data);
518+
519+
sb.AppendLine("");
520+
sb.AppendLine("```");
521+
return sb.ToString();
522+
}
523+
486524
private static string? TransformHttpFileGeneratorReport(object report)
487525
{
488526
var httpFileGeneratorReport = (HttpFileGeneratorPluginReport)report;

dev-proxy-plugins/Reporters/PlainTextReporter.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ public class PlainTextReporter(IPluginEvents pluginEvents, IProxyContext context
2424
{ typeof(HttpFileGeneratorPluginReport), TransformHttpFileGeneratorReport },
2525
{ typeof(GraphMinimalPermissionsGuidancePluginReport), TransformMinimalPermissionsGuidanceReport },
2626
{ typeof(GraphMinimalPermissionsPluginReport), TransformMinimalPermissionsReport },
27-
{ typeof(OpenApiSpecGeneratorPluginReport), TransformOpenApiSpecGeneratorReport }
27+
{ typeof(OpenApiSpecGeneratorPluginReport), TransformOpenApiSpecGeneratorReport },
28+
{ typeof(UrlDiscoveryPluginReport), TransformUrlDiscoveryReport }
2829
};
2930

3031
private const string _requestsInterceptedMessage = "Requests intercepted";
@@ -75,6 +76,36 @@ public class PlainTextReporter(IPluginEvents pluginEvents, IProxyContext context
7576
return sb.ToString();
7677
}
7778

79+
private static string? TransformUrlDiscoveryReport(object report)
80+
{
81+
var urlDiscoveryPluginReport = (UrlDiscoveryPluginReport)report;
82+
83+
var sb = new StringBuilder();
84+
sb.AppendLine("Wildcards");
85+
sb.AppendLine("");
86+
sb.AppendLine("You can use wildcards to catch multiple URLs with the same pattern.");
87+
sb.AppendLine("For example, you can use the following URL pattern to catch all API requests to");
88+
sb.AppendLine("JSON Placeholder API:");
89+
sb.AppendLine("");
90+
sb.AppendLine("https://jsonplaceholder.typicode.com/*");
91+
sb.AppendLine("");
92+
sb.AppendLine("Excluding URLs");
93+
sb.AppendLine("");
94+
sb.AppendLine("You can exclude URLs with ! to prevent them from being intercepted.");
95+
sb.AppendLine("For example, you can exclude the URL https://jsonplaceholder.typicode.com/authors");
96+
sb.AppendLine("by using the following URL pattern:");
97+
sb.AppendLine("");
98+
sb.AppendLine("!https://jsonplaceholder.typicode.com/authors");
99+
sb.AppendLine("https://jsonplaceholder.typicode.com/*");
100+
sb.AppendLine("");
101+
sb.AppendLine("Intercepted URLs:");
102+
sb.AppendLine();
103+
104+
sb.AppendJoin(Environment.NewLine, urlDiscoveryPluginReport.Data);
105+
106+
return sb.ToString();
107+
}
108+
78109
private static string? TransformExecutionSummaryByMessageType(object report)
79110
{
80111
var executionSummaryReport = (ExecutionSummaryPluginReportByMessageType)report;

dev-proxy-plugins/RequestLogs/ExecutionSummaryPlugin.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ private Task AfterRecordingStopAsync(object? sender, RecordingArgs e)
7979
{
8080
if (!e.RequestLogs.Any())
8181
{
82+
Logger.LogRequest("No messages recorded", MessageType.Skipped);
8283
return Task.CompletedTask;
8384
}
8485

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.DevProxy.Abstractions;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Microsoft.DevProxy.Plugins.RequestLogs;
9+
10+
public class UrlDiscoveryPluginReport
11+
{
12+
public required List<string> Data { get; init; }
13+
}
14+
15+
public class UrlDiscoveryPlugin(IPluginEvents pluginEvents, IProxyContext context, ILogger logger, ISet<UrlToWatch> urlsToWatch, IConfigurationSection? configSection = null) : BaseReportingPlugin(pluginEvents, context, logger, urlsToWatch, configSection)
16+
{
17+
public override string Name => nameof(UrlDiscoveryPlugin);
18+
private readonly ExecutionSummaryPluginConfiguration _configuration = new();
19+
20+
public override async Task RegisterAsync()
21+
{
22+
await base.RegisterAsync();
23+
24+
ConfigSection?.Bind(_configuration);
25+
26+
PluginEvents.AfterRecordingStop += AfterRecordingStopAsync;
27+
}
28+
29+
private Task AfterRecordingStopAsync(object? sender, RecordingArgs e)
30+
{
31+
if (!e.RequestLogs.Any())
32+
{
33+
Logger.LogRequest("No messages recorded", MessageType.Skipped);
34+
return Task.CompletedTask;
35+
}
36+
37+
UrlDiscoveryPluginReport report = new()
38+
{
39+
Data = [.. e.RequestLogs.Select(log => log.Context?.Session.HttpClient.Request.RequestUri.ToString()).Distinct().Order()]
40+
};
41+
42+
StoreReport(report, e);
43+
44+
return Task.CompletedTask;
45+
}
46+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/microsoft/dev-proxy/main/schemas/v0.24.0/rc.schema.json",
3+
"plugins": [
4+
{
5+
"name": "UrlDiscoveryPlugin",
6+
"enabled": true,
7+
"pluginPath": "~appFolder/plugins/dev-proxy-plugins.dll"
8+
},
9+
{
10+
"name": "PlainTextReporter",
11+
"enabled": true,
12+
"pluginPath": "~appFolder/plugins/dev-proxy-plugins.dll"
13+
}
14+
],
15+
"urlsToWatch": [
16+
"https://*/*"
17+
],
18+
"record": true
19+
}

0 commit comments

Comments
 (0)