Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
48 changes: 33 additions & 15 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,14 @@ csharp_style_var_for_built_in_types = true
csharp_style_var_when_type_is_apparent = true

# Expression-bodied members
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = when_on_single_line
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = when_on_single_line:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent

# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
Expand All @@ -106,11 +106,11 @@ csharp_prefer_static_local_function = true
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async

# Code-block preferences
csharp_prefer_braces = true
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = file_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_top_level_statements = true
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_style_namespace_declarations = file_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent

# Expression-level preferences
csharp_prefer_simple_default_expression = true
Expand All @@ -128,7 +128,7 @@ csharp_style_unused_value_assignment_preference = discard_variable
csharp_style_unused_value_expression_statement_preference = discard_variable

# 'using' directive preferences
csharp_using_directive_placement = outside_namespace
csharp_using_directive_placement = outside_namespace:silent

# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
Expand Down Expand Up @@ -263,4 +263,22 @@ dotnet_diagnostic.VSTHRD104.severity = none
# Add .ConfigureAwait(bool) to your await expression
dotnet_diagnostic.VSTHRD111.severity = none

dotnet_analyzer_diagnostic.severity = warning
dotnet_analyzer_diagnostic.severity = warning
csharp_style_prefer_primary_constructors = true:suggestion
csharp_prefer_system_threading_lock = true:suggestion

# CS0618: Type or member is obsolete
dotnet_diagnostic.CS0618.severity = warning

[*.{cs,vb}]
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
3 changes: 1 addition & 2 deletions DevProxy.Abstractions/DevProxy.Abstractions.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
Expand All @@ -23,7 +23,6 @@
<PackageReference Include="Newtonsoft.Json.Schema" Version="4.0.1" />
<PackageReference Include="Scriban" Version="6.2.1" />
<PackageReference Include="System.CommandLine" Version="2.0.0-beta5.25306.1" />
<PackageReference Include="Unobtanium.Web.Proxy" Version="0.1.5" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>

Expand Down
34 changes: 0 additions & 34 deletions DevProxy.Abstractions/Extensions/FuncExtensions.cs

This file was deleted.

8 changes: 7 additions & 1 deletion DevProxy.Abstractions/Extensions/ILoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ namespace Microsoft.Extensions.Logging;

public static class ILoggerExtensions
{
public static void LogRequest(this ILogger logger, string message, MessageType messageType, LoggingContext? context = null)
public static void LogRequest(this ILogger logger, string message, MessageType messageType, object? context = null)
{
ArgumentNullException.ThrowIfNull(logger);
logger.Log(new RequestLog(message, messageType, context));
}

Expand All @@ -17,6 +18,11 @@ public static void LogRequest(this ILogger logger, string message, MessageType m
logger.Log(new RequestLog(message, messageType, method, url));
}

public static void LogRequest(this ILogger logger, string message, MessageType messageType, HttpRequestMessage httpRequestMessage, string? requestId = null, HttpResponseMessage? httpResponse = null)
{
logger.Log(new RequestLog(message, messageType, httpRequestMessage, requestId, httpResponse));
}

public static void Log(this ILogger logger, RequestLog message)
{
ArgumentNullException.ThrowIfNull(logger);
Expand Down
60 changes: 34 additions & 26 deletions DevProxy.Abstractions/Plugins/BasePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,57 @@ public abstract class BasePlugin(
ILogger logger,
ISet<UrlToWatch> urlsToWatch) : IPlugin
{
/// <inheritdoc/>
public bool Enabled { get; protected set; } = true;
protected ILogger Logger { get; } = logger;
protected ISet<UrlToWatch> UrlsToWatch { get; } = urlsToWatch;

/// <summary>
/// List of URLs to watch for this plugin.
/// </summary>
public ISet<UrlToWatch> UrlsToWatch { get; } = urlsToWatch;
/// <inheritdoc/>
public abstract string Name { get; }
protected ILogger Logger { get; } = logger;
/// <inheritdoc/>
public virtual Func<RequestArguments, CancellationToken, Task<PluginResponse>>? OnRequestAsync { get; }

public virtual Option[] GetOptions() => [];
public virtual Command[] GetCommands() => [];
/// <inheritdoc/>
public virtual Func<RequestArguments, CancellationToken, Task>? ProvideRequestGuidanceAsync { get; }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the benefit of using delegates vs. virtual methods that we can override? Since we need to implement the logic anyway, isn't using a delegate an unnecessary complexity? Ie. I need to implement the method and add a reference to it in the delegate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The benefit is performance, before calling the plugin we already know if it implemented this. So in the ProxyEngine it skips plugins that did not implement this. We can do the null check on the function, instead of the base plugin returning completed task

And on the other hand, this way we force the plugin to think about what it is doing next, early response/modify request/continue as normal.


public virtual Task InitializeAsync(InitArgs e, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <inheritdoc/>
public virtual Func<ResponseArguments, CancellationToken, Task<PluginResponse?>>? OnResponseAsync { get; }

public virtual void OptionsLoaded(OptionsLoadedArgs e)
{
}
/// <inheritdoc/>
public virtual Func<ResponseArguments, CancellationToken, Task>? ProvideResponseGuidanceAsync { get; }

public virtual Task BeforeRequestAsync(ProxyRequestArgs e, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <inheritdoc/>
public virtual Func<RequestLogArgs, CancellationToken, Task>? HandleRequestLogAsync { get; }

public virtual Task BeforeResponseAsync(ProxyResponseArgs e, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <inheritdoc/>
public virtual Func<RecordingArgs, CancellationToken, Task>? HandleRecordingStopAsync { get; }

public virtual Task AfterResponseAsync(ProxyResponseArgs e, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
/// <inheritdoc/>
public virtual Option[] GetOptions() => [];
/// <inheritdoc/>
public virtual Command[] GetCommands() => [];

public virtual Task AfterRequestLogAsync(RequestLogArgs e, CancellationToken cancellationToken)
/// <inheritdoc/>
public virtual Task InitializeAsync(InitArgs e, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}

public virtual Task AfterRecordingStopAsync(RecordingArgs e, CancellationToken cancellationToken)
/// <inheritdoc/>
public virtual void OptionsLoaded(OptionsLoadedArgs e)
{
return Task.CompletedTask;
}

///// <inheritdoc/>
//public virtual Task AfterRecordingStopAsync(RecordingArgs e, CancellationToken cancellationToken)
//{
// return Task.CompletedTask;
//}

/// <inheritdoc/>
public virtual Task MockRequestAsync(EventArgs e, CancellationToken cancellationToken)
{
return Task.CompletedTask;
Expand Down
18 changes: 10 additions & 8 deletions DevProxy.Abstractions/Plugins/BaseReportingPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ namespace DevProxy.Abstractions.Plugins;

public abstract class BaseReportingPlugin(
ILogger logger,
ISet<UrlToWatch> urlsToWatch) : BasePlugin(logger, urlsToWatch)
ISet<UrlToWatch> urlsToWatch,
IProxyStorage proxyStorage) : BasePlugin(logger, urlsToWatch)
{
protected virtual void StoreReport(object report, ProxyEventArgsBase e)
protected IProxyStorage ProxyStorage => proxyStorage;
protected virtual void StoreReport(object report)
{
ArgumentNullException.ThrowIfNull(e);

if (report is null)
{
return;
}

((Dictionary<string, object>)e.GlobalData[ProxyUtils.ReportsKey])[Name] = report;
((Dictionary<string, object>)ProxyStorage.GlobalData[ProxyUtils.ReportsKey])[Name] = report;
}
}

Expand All @@ -31,23 +32,24 @@ public abstract class BaseReportingPlugin<TConfiguration>(
ILogger logger,
ISet<UrlToWatch> urlsToWatch,
IProxyConfiguration proxyConfiguration,
IConfigurationSection configurationSection) :
IConfigurationSection configurationSection,
IProxyStorage proxyStorage) :
BasePlugin<TConfiguration>(
httpClient,
logger,
urlsToWatch,
proxyConfiguration,
configurationSection) where TConfiguration : new()
{
protected virtual void StoreReport(object report, ProxyEventArgsBase e)
protected IProxyStorage ProxyStorage => proxyStorage;
protected virtual void StoreReport(object report)
{
ArgumentNullException.ThrowIfNull(e);

if (report is null)
{
return;
}

((Dictionary<string, object>)e.GlobalData[ProxyUtils.ReportsKey])[Name] = report;
((Dictionary<string, object>)ProxyStorage.GlobalData[ProxyUtils.ReportsKey])[Name] = report;
}
}
86 changes: 81 additions & 5 deletions DevProxy.Abstractions/Plugins/IPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,96 @@

namespace DevProxy.Abstractions.Plugins;

/// <summary>
/// The interface that all plugins must implement.
/// </summary>
/// <remarks>We made it easy for you with the <see cref="BasePlugin"/></remarks>
public interface IPlugin
{
/// <summary>
/// Name of the plugin.
/// </summary>
string Name { get; }

/// <summary>
/// Whether the plugin is enabled or not.
/// </summary>
bool Enabled { get; }
Option[] GetOptions();
Command[] GetCommands();

/// <summary>
/// Called once after the plugin is constructed, but before any requests are handled.
/// </summary>
/// <param name="e"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task InitializeAsync(InitArgs e, CancellationToken cancellationToken);

/// <summary>
/// Handles the event triggered when options are successfully loaded.
/// </summary>
/// <param name="e">An <see cref="OptionsLoadedArgs"/> instance containing the event data, including the loaded options.</param>
void OptionsLoaded(OptionsLoadedArgs e);
Task BeforeRequestAsync(ProxyRequestArgs e, CancellationToken cancellationToken);
Task BeforeResponseAsync(ProxyResponseArgs e, CancellationToken cancellationToken);
Task AfterResponseAsync(ProxyResponseArgs e, CancellationToken cancellationToken);
Task AfterRequestLogAsync(RequestLogArgs e, CancellationToken cancellationToken);
Task AfterRecordingStopAsync(RecordingArgs e, CancellationToken cancellationToken);


/// <summary>
/// Implement this to handle requests.
/// </summary>
/// <remarks>This is <see langword="null"/> by default, so we can filter plugins based on implementation.</remarks>
Func<RequestArguments, CancellationToken, Task<PluginResponse>>? OnRequestAsync { get; }

/// <summary>
/// Implement this to provide guidance for requests, you cannot modify the request or response here.
/// </summary>
/// <remarks>This is <see langword="null"/> by default, so we can filter plugins based on implementation.</remarks>
Func<RequestArguments, CancellationToken, Task>? ProvideRequestGuidanceAsync { get; }

/// <summary>
/// Implement this to modify responses from the remote server.
/// </summary>
/// <remarks>This is <see langword="null"/> by default, so we can filter plugins based on implementation.</remarks>
Func<ResponseArguments, CancellationToken, Task<PluginResponse?>>? OnResponseAsync { get; }

/// <summary>
/// Implement this to provide guidance based on responses from the remote server.
/// </summary>
/// <remarks>Think caching after the fact, combined with <see cref="OnRequestAsync"/>. This is <see langword="null"/> by default, so we can filter plugins based on implementation.</remarks>
Func<ResponseArguments, CancellationToken, Task>? ProvideResponseGuidanceAsync { get; }

/// <summary>
/// Implement this to receive RequestLog messages for each <see cref="Microsoft.Extensions.Logging.ILoggerExtensions.LogRequest(Microsoft.Extensions.Logging.ILogger, string, MessageType, HttpRequestMessage)"/> call.
/// </summary>
Func<RequestLogArgs, CancellationToken, Task>? HandleRequestLogAsync { get; }

/// <summary>
/// Executes post-processing tasks after a recording has stopped.
/// </summary>
Func<RecordingArgs, CancellationToken, Task>? HandleRecordingStopAsync { get; }

/// <summary>
/// Receiving RequestLog messages for each <see cref="Microsoft.Extensions.Logging.ILoggerExtensions.LogRequest(Microsoft.Extensions.Logging.ILogger, string, MessageType, HttpRequestMessage)"/> call.
/// </summary>
/// <remarks>This is for collecting log messages not requests itself</remarks>
/// <param name="e"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
//Task AfterRequestLogAsync(RequestLogArgs e, CancellationToken cancellationToken);

/// <summary>
/// Executes post-processing tasks after a recording has stopped.
/// </summary>
/// <param name="e">The arguments containing details about the recording that has stopped.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
//Task AfterRecordingStopAsync(RecordingArgs e, CancellationToken cancellationToken);

/// <summary>
///
/// </summary>
/// <param name="e"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task MockRequestAsync(EventArgs e, CancellationToken cancellationToken);
}

Expand Down
Loading