diff --git a/API/Content/Services/IResult.cs b/API/Content/Services/IResult.cs new file mode 100644 index 00000000..0f4a0680 --- /dev/null +++ b/API/Content/Services/IResult.cs @@ -0,0 +1,15 @@ +using GenHTTP.Api.Protocol; + +namespace GenHTTP.Api.Content.Services +{ + + public interface IResult + { + + object? Payload { get; } + + void Apply(IResponseBuilder builder); + + } + +} diff --git a/API/Content/Services/Result.cs b/API/Content/Services/Result.cs new file mode 100644 index 00000000..4e48a013 --- /dev/null +++ b/API/Content/Services/Result.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; + +using GenHTTP.Api.Protocol; + +namespace GenHTTP.Api.Content.Services +{ + + public class Result : IResult, IResponseModification> + { + private FlexibleResponseStatus? _Status; + + private Dictionary? _Headers; + + private DateTime? _Expires; + + private DateTime? _Modified; + + private List? _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 Status(ResponseStatus status) + { + _Status = new(status); + return this; + } + + public Result Status(int status, string reason) + { + _Status = new FlexibleResponseStatus(status, reason); + return this; + } + + public Result Header(string key, string value) + { + if (_Headers == null) + { + _Headers = new(); + } + + _Headers[key] = value; + + return this; + } + + public Result Expires(DateTime expiryDate) + { + _Expires = expiryDate; + return this; + } + + public Result Modified(DateTime modificationDate) + { + _Modified = modificationDate; + return this; + } + + public Result Cookie(Cookie cookie) + { + if (_Cookies == null) + { + _Cookies = new(); + } + + _Cookies.Add(cookie); + + return this; + } + + public Result Type(FlexibleContentType contentType) + { + _ContentType = contentType; + return this; + } + + public Result 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 + + } + +} diff --git a/API/Protocol/IResponseBuilder.cs b/API/Protocol/IResponseBuilder.cs index bc529f14..d34efcfd 100644 --- a/API/Protocol/IResponseBuilder.cs +++ b/API/Protocol/IResponseBuilder.cs @@ -1,6 +1,4 @@ -using System; - -using GenHTTP.Api.Infrastructure; +using GenHTTP.Api.Infrastructure; namespace GenHTTP.Api.Protocol { @@ -8,7 +6,7 @@ namespace GenHTTP.Api.Protocol /// /// Allows to configure a HTTP response to be send. /// - public interface IResponseBuilder : IBuilder + public interface IResponseBuilder : IBuilder, IResponseModification { /// @@ -16,70 +14,18 @@ public interface IResponseBuilder : IBuilder /// IRequest Request { get; } - /// - /// Specifies the HTTP status code of the response. - /// - /// The HTTP status code of the response - IResponseBuilder Status(ResponseStatus status); - - /// - /// Specifies the HTTP status code of the response. - /// - /// The status code of the response - /// The reason phrase of the response (such as "Not Found" for 404) - IResponseBuilder Status(int status, string reason); - - /// - /// Sets the given header field on the response. Changing HTTP - /// protocol headers may cause incorrect behavior. - /// - /// The name of the header to be set - /// The value of the header field - IResponseBuilder Header(string key, string value); - - /// - /// Sets the expiration date of the response. - /// - /// The expiration date of the response - IResponseBuilder Expires(DateTime expiryDate); - - /// - /// Sets the point in time when the requested resource has been - /// modified last. - /// - /// The point in time when the requested resource has been modified last - IResponseBuilder Modified(DateTime modificationDate); - - /// - /// Adds the given cookie to the response. - /// - /// The cookie to be added - IResponseBuilder Cookie(Cookie cookie); - /// /// Specifies the content to be sent to the client. /// /// The content to be send to the client IResponseBuilder Content(IResponseContent content); - /// - /// Specifies the content type of this response. - /// - /// The content type of this response - IResponseBuilder Type(FlexibleContentType contentType); - /// /// Specifies the length of the content stream, if known. /// /// The length of the content stream IResponseBuilder Length(ulong length); - /// - /// Sets the encoding of the content. - /// - /// The encoding of the content - IResponseBuilder Encoding(string encoding); - } } diff --git a/API/Protocol/IResponseModification.cs b/API/Protocol/IResponseModification.cs new file mode 100644 index 00000000..9f7e73cf --- /dev/null +++ b/API/Protocol/IResponseModification.cs @@ -0,0 +1,63 @@ +using System; + +namespace GenHTTP.Api.Protocol +{ + + public interface IResponseModification + { + + /// + /// Specifies the HTTP status code of the response. + /// + /// The HTTP status code of the response + T Status(ResponseStatus status); + + /// + /// Specifies the HTTP status code of the response. + /// + /// The status code of the response + /// The reason phrase of the response (such as "Not Found" for 404) + T Status(int status, string reason); + + /// + /// Sets the given header field on the response. Changing HTTP + /// protocol headers may cause incorrect behavior. + /// + /// The name of the header to be set + /// The value of the header field + T Header(string key, string value); + + /// + /// Sets the expiration date of the response. + /// + /// The expiration date of the response + T Expires(DateTime expiryDate); + + /// + /// Sets the point in time when the requested resource has been + /// modified last. + /// + /// The point in time when the requested resource has been modified last + T Modified(DateTime modificationDate); + + /// + /// Adds the given cookie to the response. + /// + /// The cookie to be added + T Cookie(Cookie cookie); + + /// + /// Specifies the content type of this response. + /// + /// The content type of this response + T Type(FlexibleContentType contentType); + + /// + /// Sets the encoding of the content. + /// + /// The encoding of the content + T Encoding(string encoding); + + } + +} diff --git a/Modules/Controllers/Provider/ControllerHandler.cs b/Modules/Controllers/Provider/ControllerHandler.cs index 97553931..79ebee04 100644 --- a/Modules/Controllers/Provider/ControllerHandler.cs +++ b/Modules/Controllers/Provider/ControllerHandler.cs @@ -52,7 +52,7 @@ private IEnumerable> 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); } } diff --git a/Modules/Functional/Provider/InlineHandler.cs b/Modules/Functional/Provider/InlineHandler.cs index 54d632e3..d1383e77 100644 --- a/Modules/Functional/Provider/InlineHandler.cs +++ b/Modules/Functional/Provider/InlineHandler.cs @@ -44,7 +44,7 @@ private IEnumerable> AnalyzeMethods(List 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); } } diff --git a/Modules/Reflection/Adjustments.cs b/Modules/Reflection/Adjustments.cs new file mode 100644 index 00000000..1d2af559 --- /dev/null +++ b/Modules/Reflection/Adjustments.cs @@ -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? adjustments) + { + if (adjustments != null) + { + adjustments(builder); + } + + return builder; + } + + } + +} diff --git a/Modules/Reflection/MethodHandler.cs b/Modules/Reflection/MethodHandler.cs index a00735dd..d115826c 100644 --- a/Modules/Reflection/MethodHandler.cs +++ b/Modules/Reflection/MethodHandler.cs @@ -49,7 +49,7 @@ public sealed class MethodHandler : IHandler private Func InstanceProvider { get; } - private Func> ResponseProvider { get; } + private Func?, ValueTask> ResponseProvider { get; } private SerializationRegistry Serialization { get; } @@ -60,7 +60,7 @@ public sealed class MethodHandler : IHandler #region Initialization public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, Func instanceProvider, IMethodConfiguration metaData, - Func> responseProvider, SerializationRegistry serialization, InjectionRegistry injection) + Func?, ValueTask> responseProvider, SerializationRegistry serialization, InjectionRegistry injection) { Parent = parent; @@ -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 GetArguments(IRequest request) diff --git a/Modules/Reflection/ResponseProvider.cs b/Modules/Reflection/ResponseProvider.cs index 347229ea..74cb87e3 100644 --- a/Modules/Reflection/ResponseProvider.cs +++ b/Modules/Reflection/ResponseProvider.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using GenHTTP.Api.Content; +using GenHTTP.Api.Content.Services; using GenHTTP.Api.Protocol; using GenHTTP.Modules.Basics; @@ -36,22 +37,29 @@ public ResponseProvider(SerializationRegistry? serialization) #region Functionality - public async ValueTask GetResponse(IRequest request, IHandler handler, object? result) + public async ValueTask GetResponseAsync(IRequest request, IHandler handler, object? result, Action? adjustments = null) { // no result = 204 if (result is null) { return request.Respond() .Status(ResponseStatus.NoContent) + .Adjust(adjustments) .Build(); } var type = result.GetType(); + // unwrap the result if applicable + if (type is IResult wrapped) + { + return await GetResponseAsync(request, handler, wrapped.Payload, (b) => wrapped.Apply(b)).ConfigureAwait(false); + } + // response returned by the method if (result is IResponseBuilder responseBuilder) { - return responseBuilder.Build(); + return responseBuilder.Adjust(adjustments).Build(); } if (result is IResponse response) @@ -79,6 +87,7 @@ public ResponseProvider(SerializationRegistry? serialization) var downloadResponse = request.Respond() .Content(download, () => download.CalculateChecksumAsync()) .Type(ContentType.ApplicationForceDownload) + .Adjust(adjustments) .Build(); return downloadResponse; @@ -92,6 +101,7 @@ public ResponseProvider(SerializationRegistry? serialization) return request.Respond() .Content(result.ToString() ?? string.Empty) .Type(ContentType.TextPlain) + .Adjust(adjustments) .Build(); } @@ -103,12 +113,14 @@ public ResponseProvider(SerializationRegistry? serialization) throw new ProviderException(ResponseStatus.UnsupportedMediaType, "Requested format is not supported"); } - var serializedResult = await serializer.SerializeAsync(request, result).ConfigureAwait(false); + var serializedResult = await serializer.SerializeAsync(request, result) + .ConfigureAwait(false); - return serializedResult.Build(); + return serializedResult.Adjust(adjustments) + .Build(); } - throw new ProviderException(ResponseStatus.InternalServerError, "Result type must be one of: IHandlerBuilder, IHandler, IResponseBuilder, IResponse"); + throw new ProviderException(ResponseStatus.InternalServerError, "Result type must be one of: IHandlerBuilder, IHandler, IResponseBuilder, IResponse, Stream"); } #endregion diff --git a/Modules/Webservices/Provider/ServiceResourceRouter.cs b/Modules/Webservices/Provider/ServiceResourceRouter.cs index c2d05e5d..d87388d3 100644 --- a/Modules/Webservices/Provider/ServiceResourceRouter.cs +++ b/Modules/Webservices/Provider/ServiceResourceRouter.cs @@ -51,7 +51,7 @@ private IEnumerable> AnalyzeMethods(Type type, Ser { var path = PathArguments.Route(attribute.Path); - yield return (parent) => new MethodHandler(parent, method, path, () => Instance, attribute, ResponseProvider.GetResponse, serialization, injection); + yield return (parent) => new MethodHandler(parent, method, path, () => Instance, attribute, ResponseProvider.GetResponseAsync, serialization, injection); } } }