Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 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
4 changes: 2 additions & 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,7 @@
<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="Unobtanium.Web.Proxy.Events" Version="0.9.1-beta.2" />
<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
25 changes: 24 additions & 1 deletion DevProxy.Abstractions/Plugins/BasePlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,33 @@ public abstract class BasePlugin(
{
public bool Enabled { get; protected set; } = true;
protected ILogger Logger { get; } = logger;
protected ISet<UrlToWatch> UrlsToWatch { get; } = urlsToWatch;
public ISet<UrlToWatch> UrlsToWatch { get; } = urlsToWatch;

public abstract string Name { get; }

/// <summary>
/// Implement this to handle requests, if you won't be modifying requests or respond, use <see cref="OnRequestLogAsync"/>.
/// </summary>
/// <remarks>This is <see langword="null"/> by default, so we can filter plugins based on implementation.</remarks>
public virtual Func<RequestArguments, CancellationToken, Task<PluginResponse>>? OnRequestAsync { get; }

/// <summary>
/// Implement this to log 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>
public virtual Func<RequestArguments, CancellationToken, Task>? OnRequestLogAsync { 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>
public virtual Func<ResponseArguments, CancellationToken, Task<PluginResponse?>>? OnResponseAsync { get; }

/// <summary>
/// Implement this to modify responses from the remote server.
/// </summary>
public virtual Func<ResponseArguments, CancellationToken, Task>? OnResponseLogAsync { get; }

public virtual Option[] GetOptions() => [];
public virtual Command[] GetCommands() => [];

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;
}
}
41 changes: 41 additions & 0 deletions DevProxy.Abstractions/Plugins/IPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,51 @@ public interface IPlugin

Task InitializeAsync(InitArgs e, CancellationToken cancellationToken);
void OptionsLoaded(OptionsLoadedArgs e);


/// <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 log 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>? OnRequestLogAsync { 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 log 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>? OnResponseLogAsync { get; }

Task BeforeRequestAsync(ProxyRequestArgs e, CancellationToken cancellationToken);
Task BeforeResponseAsync(ProxyResponseArgs e, CancellationToken cancellationToken);
Task AfterResponseAsync(ProxyResponseArgs e, CancellationToken cancellationToken);

/// <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);
Task MockRequestAsync(EventArgs e, CancellationToken cancellationToken);
}
Expand Down
23 changes: 23 additions & 0 deletions DevProxy.Abstractions/Plugins/IProxyStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DevProxy")]
namespace DevProxy.Abstractions.Plugins;

/// <summary>
/// If you need either global or request-specific storage, ask for this interface in your plugin.
/// </summary>
public interface IProxyStorage
{
/// <summary>
/// Access to global data shared across all requests.
/// </summary>
public Dictionary<string, object> GlobalData { get; }

/// <summary>
/// Get request-specific data by its ID.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>

public Dictionary<string, object> GetRequestData(RequestId id);

internal void RemoveRequestData(RequestId id);
}
5 changes: 2 additions & 3 deletions DevProxy.Abstractions/Plugins/PluginEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using Titanium.Web.Proxy.Http;

namespace DevProxy.Abstractions.Plugins;

public class ThrottlerInfo(string throttlingKey, Func<Request, string, ThrottlingInfo> shouldThrottle, DateTime resetTime)
public class ThrottlerInfo(string throttlingKey, Func<HttpRequestMessage, string, ThrottlingInfo> shouldThrottle, DateTime resetTime)
{
/// <summary>
/// Time when the throttling window will be reset
Expand All @@ -20,7 +19,7 @@ public class ThrottlerInfo(string throttlingKey, Func<Request, string, Throttlin
/// Returns an instance of ThrottlingInfo that contains information
/// whether the request should be throttled or not.
/// </summary>
public Func<Request, string, ThrottlingInfo> ShouldThrottle { get; private set; } = shouldThrottle ?? throw new ArgumentNullException(nameof(shouldThrottle));
public Func<HttpRequestMessage, string, ThrottlingInfo> ShouldThrottle { get; private set; } = shouldThrottle ?? throw new ArgumentNullException(nameof(shouldThrottle));
/// <summary>
/// Throttling key used to identify which requests should be throttled.
/// Can be set to a hostname, full URL or a custom string value, that
Expand Down
15 changes: 15 additions & 0 deletions DevProxy.Abstractions/Plugins/PluginResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace DevProxy.Abstractions.Plugins;
public class PluginResponse
{
public HttpRequestMessage? Request { get; private set; }
public HttpResponseMessage? Response { get; private set; }
private PluginResponse(HttpResponseMessage? response, HttpRequestMessage? request)
{
Response = response;
Request = request;
}

public static PluginResponse Continue() => new(null, null);
public static PluginResponse Continue(HttpRequestMessage request) => new(null, request);
public static PluginResponse Respond(HttpResponseMessage response) => new(response, null);
}
26 changes: 26 additions & 0 deletions DevProxy.Abstractions/Plugins/RequestArguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace DevProxy.Abstractions.Plugins;
public class RequestArguments(HttpRequestMessage request, string requestId)
{
public HttpRequestMessage Request { get; } = request;
public RequestId RequestId { get; } = requestId ?? throw new ArgumentNullException(nameof(requestId));
}

public record RequestId(string Id)
{
private string Id { get; } = Id ?? throw new ArgumentNullException(nameof(Id));
public static implicit operator string(RequestId requestId)
{
ArgumentNullException.ThrowIfNull(requestId);
return requestId.Id;
}

public static implicit operator RequestId(string id)
{
return new(id);
}

public static RequestId FromString(string id)
{
return new RequestId(id);
}
}
5 changes: 5 additions & 0 deletions DevProxy.Abstractions/Plugins/ResponseArguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace DevProxy.Abstractions.Plugins;
public class ResponseArguments(HttpRequestMessage request, HttpResponseMessage response, string requestId) : RequestArguments(request, requestId)
{
public HttpResponseMessage Response { get; } = response;
}
Loading