Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add result type which allows to do response modifications #439

Merged
merged 5 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
15 changes: 15 additions & 0 deletions API/Content/Services/IResult.cs
Kaliumhexacyanoferrat marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using GenHTTP.Api.Protocol;

namespace GenHTTP.Api.Content.Services
{

public interface IResult
{

object? Payload { get; }

void Apply(IResponseBuilder builder);

}

}
154 changes: 154 additions & 0 deletions API/Content/Services/Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;

using GenHTTP.Api.Protocol;

namespace GenHTTP.Api.Content.Services
{

public class Result<T> : IResult, IResponseModification<Result<T>>
{
private FlexibleResponseStatus? _Status;

private Dictionary<string, string>? _Headers;

private DateTime? _Expires;

private DateTime? _Modified;

private List<Cookie>? _Cookies;

private FlexibleContentType? _ContentType;

private string? _Encoding;

#region Get-/Setters

public T? Payload { get; }

object? IResult.Payload => Payload;

#endregion

#region Initialization

public Result(T? payload)
{
Payload = payload;
}

#endregion

#region Functionality

public Result<T> Status(ResponseStatus status)
{
_Status = new(status);
return this;
}

public Result<T> Status(int status, string reason)
{
_Status = new FlexibleResponseStatus(status, reason);
return this;
}

public Result<T> Header(string key, string value)
{
if (_Headers == null)
{
_Headers = new();
}

_Headers[key] = value;

return this;
}

public Result<T> Expires(DateTime expiryDate)
{
_Expires = expiryDate;
return this;
}

public Result<T> Modified(DateTime modificationDate)
{
_Modified = modificationDate;
return this;
}

public Result<T> Cookie(Cookie cookie)
{
if (_Cookies == null)
{
_Cookies = new();
}

_Cookies.Add(cookie);

return this;
}

public Result<T> Type(FlexibleContentType contentType)
{
_ContentType = contentType;
return this;
}

public Result<T> Encoding(string encoding)
{
_Encoding = encoding;
return this;
}

public void Apply(IResponseBuilder builder)
{
if (_Status != null)
{
var value = _Status.Value;

builder.Status(value.RawStatus, value.Phrase);
}

if (_Headers != null)
{
foreach (var kv in _Headers)
{
builder.Header(kv.Key, kv.Value);
}
}

if (_Expires != null)
{
builder.Expires(_Expires.Value);
}

if (_Modified != null)
{
builder.Modified(_Modified.Value);
}

if (_Cookies != null)
{
foreach (var cookie in _Cookies)
{
builder.Cookie(cookie);
}
}

if (_ContentType is not null)
{
builder.Type(_ContentType);
}

if (_Encoding != null)
{
builder.Encoding(_Encoding);
}
}

#endregion

}

}
58 changes: 2 additions & 56 deletions API/Protocol/IResponseBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,85 +1,31 @@
using System;

using GenHTTP.Api.Infrastructure;
using GenHTTP.Api.Infrastructure;

namespace GenHTTP.Api.Protocol
{

/// <summary>
/// Allows to configure a HTTP response to be send.
/// </summary>
public interface IResponseBuilder : IBuilder<IResponse>
public interface IResponseBuilder : IBuilder<IResponse>, IResponseModification<IResponseBuilder>
{

/// <summary>
/// The request the response belongs to.
/// </summary>
IRequest Request { get; }

/// <summary>
/// Specifies the HTTP status code of the response.
/// </summary>
/// <param name="status">The HTTP status code of the response</param>
IResponseBuilder Status(ResponseStatus status);

/// <summary>
/// Specifies the HTTP status code of the response.
/// </summary>
/// <param name="status">The status code of the response</param>
/// <param name="reason">The reason phrase of the response (such as "Not Found" for 404)</param>
IResponseBuilder Status(int status, string reason);

/// <summary>
/// Sets the given header field on the response. Changing HTTP
/// protocol headers may cause incorrect behavior.
/// </summary>
/// <param name="key">The name of the header to be set</param>
/// <param name="value">The value of the header field</param>
IResponseBuilder Header(string key, string value);

/// <summary>
/// Sets the expiration date of the response.
/// </summary>
/// <param name="expiryDate">The expiration date of the response</param>
IResponseBuilder Expires(DateTime expiryDate);

/// <summary>
/// Sets the point in time when the requested resource has been
/// modified last.
/// </summary>
/// <param name="modificationDate">The point in time when the requested resource has been modified last</param>
IResponseBuilder Modified(DateTime modificationDate);

/// <summary>
/// Adds the given cookie to the response.
/// </summary>
/// <param name="cookie">The cookie to be added</param>
IResponseBuilder Cookie(Cookie cookie);

/// <summary>
/// Specifies the content to be sent to the client.
/// </summary>
/// <param name="content">The content to be send to the client</param>
IResponseBuilder Content(IResponseContent content);

/// <summary>
/// Specifies the content type of this response.
/// </summary>
/// <param name="contentType">The content type of this response</param>
IResponseBuilder Type(FlexibleContentType contentType);

/// <summary>
/// Specifies the length of the content stream, if known.
/// </summary>
/// <param name="length">The length of the content stream</param>
IResponseBuilder Length(ulong length);

/// <summary>
/// Sets the encoding of the content.
/// </summary>
/// <param name="encoding">The encoding of the content</param>
IResponseBuilder Encoding(string encoding);

}

}
63 changes: 63 additions & 0 deletions API/Protocol/IResponseModification.cs
Copy link
Collaborator

Choose a reason for hiding this comment

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

API looks good to me; it's essentially what I've already put together on my own, but with the benefit that it handles the response building implicitly under the hood, and since all it does is modify a default response, it removes a lot of the busywork that's necessary when building a response manually. Very nice.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;

namespace GenHTTP.Api.Protocol
{

public interface IResponseModification<T>

Check warning on line 6 in API/Protocol/IResponseModification.cs

View workflow job for this annotation

GitHub Actions / build

Add the 'out' keyword to parameter 'T' to make it 'covariant'. (https://rules.sonarsource.com/csharp/RSPEC-3246)

Check warning on line 6 in API/Protocol/IResponseModification.cs

View workflow job for this annotation

GitHub Actions / build

Add the 'out' keyword to parameter 'T' to make it 'covariant'. (https://rules.sonarsource.com/csharp/RSPEC-3246)

Check warning on line 6 in API/Protocol/IResponseModification.cs

View workflow job for this annotation

GitHub Actions / build

Add the 'out' keyword to parameter 'T' to make it 'covariant'. (https://rules.sonarsource.com/csharp/RSPEC-3246)

Check warning on line 6 in API/Protocol/IResponseModification.cs

View workflow job for this annotation

GitHub Actions / build

Add the 'out' keyword to parameter 'T' to make it 'covariant'. (https://rules.sonarsource.com/csharp/RSPEC-3246)

Check warning on line 6 in API/Protocol/IResponseModification.cs

View workflow job for this annotation

GitHub Actions / build

Add the 'out' keyword to parameter 'T' to make it 'covariant'. (https://rules.sonarsource.com/csharp/RSPEC-3246)

Check warning on line 6 in API/Protocol/IResponseModification.cs

View workflow job for this annotation

GitHub Actions / build

Add the 'out' keyword to parameter 'T' to make it 'covariant'. (https://rules.sonarsource.com/csharp/RSPEC-3246)

Check warning on line 6 in API/Protocol/IResponseModification.cs

View workflow job for this annotation

GitHub Actions / build

Add the 'out' keyword to parameter 'T' to make it 'covariant'. (https://rules.sonarsource.com/csharp/RSPEC-3246)
{

/// <summary>
/// Specifies the HTTP status code of the response.
/// </summary>
/// <param name="status">The HTTP status code of the response</param>
T Status(ResponseStatus status);

/// <summary>
/// Specifies the HTTP status code of the response.
/// </summary>
/// <param name="status">The status code of the response</param>
/// <param name="reason">The reason phrase of the response (such as "Not Found" for 404)</param>
T Status(int status, string reason);

/// <summary>
/// Sets the given header field on the response. Changing HTTP
/// protocol headers may cause incorrect behavior.
/// </summary>
/// <param name="key">The name of the header to be set</param>
/// <param name="value">The value of the header field</param>
T Header(string key, string value);

/// <summary>
/// Sets the expiration date of the response.
/// </summary>
/// <param name="expiryDate">The expiration date of the response</param>
T Expires(DateTime expiryDate);

/// <summary>
/// Sets the point in time when the requested resource has been
/// modified last.
/// </summary>
/// <param name="modificationDate">The point in time when the requested resource has been modified last</param>
T Modified(DateTime modificationDate);

/// <summary>
/// Adds the given cookie to the response.
/// </summary>
/// <param name="cookie">The cookie to be added</param>
T Cookie(Cookie cookie);

/// <summary>
/// Specifies the content type of this response.
/// </summary>
/// <param name="contentType">The content type of this response</param>
T Type(FlexibleContentType contentType);

/// <summary>
/// Sets the encoding of the content.
/// </summary>
/// <param name="encoding">The encoding of the content</param>
T Encoding(string encoding);

}

}
2 changes: 1 addition & 1 deletion Modules/Controllers/Provider/ControllerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ private IEnumerable<Func<IHandler, MethodHandler>> AnalyzeMethods(Type type, Ser

var path = DeterminePath(method, arguments);

yield return (parent) => new MethodHandler(parent, method, path, () => new T(), annotation, ResponseProvider.GetResponse, formats, injection);
yield return (parent) => new MethodHandler(parent, method, path, () => new T(), annotation, ResponseProvider.GetResponseAsync, formats, injection);
}
}

Expand Down
2 changes: 1 addition & 1 deletion Modules/Functional/Provider/InlineHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private IEnumerable<Func<IHandler, MethodHandler>> AnalyzeMethods(List<InlineFun

var target = function.Delegate.Target ?? throw new InvalidOperationException("Delegate target must not be null");

yield return (parent) => new MethodHandler(parent, function.Delegate.Method, path, () => target, function.Configuration, ResponseProvider.GetResponse, formats, injection);
yield return (parent) => new MethodHandler(parent, function.Delegate.Method, path, () => target, function.Configuration, ResponseProvider.GetResponseAsync, formats, injection);
}
}

Expand Down
23 changes: 23 additions & 0 deletions Modules/Reflection/Adjustments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System;

using GenHTTP.Api.Protocol;

namespace GenHTTP.Modules.Reflection
{

internal static class Adjustments
{

internal static IResponseBuilder Adjust(this IResponseBuilder builder, Action<IResponseBuilder>? adjustments)
{
if (adjustments != null)
{
adjustments(builder);
}

return builder;
}

}

}
6 changes: 3 additions & 3 deletions Modules/Reflection/MethodHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public sealed class MethodHandler : IHandler

private Func<object> InstanceProvider { get; }

private Func<IRequest, IHandler, object?, ValueTask<IResponse?>> ResponseProvider { get; }
private Func<IRequest, IHandler, object?, Action<IResponseBuilder>?, ValueTask<IResponse?>> ResponseProvider { get; }

private SerializationRegistry Serialization { get; }

Expand All @@ -60,7 +60,7 @@ public sealed class MethodHandler : IHandler
#region Initialization

public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, Func<object> instanceProvider, IMethodConfiguration metaData,
Func<IRequest, IHandler, object?, ValueTask<IResponse?>> responseProvider, SerializationRegistry serialization, InjectionRegistry injection)
Func<IRequest, IHandler, object?, Action<IResponseBuilder>?, ValueTask<IResponse?>> responseProvider, SerializationRegistry serialization, InjectionRegistry injection)
{
Parent = parent;

Expand Down Expand Up @@ -88,7 +88,7 @@ public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing,

var result = Invoke(arguments);

return await ResponseProvider(request, this, await UnwrapAsync(result));
return await ResponseProvider(request, this, await UnwrapAsync(result), null);
}

private async ValueTask<object?[]> GetArguments(IRequest request)
Expand Down
Loading
Loading