diff --git a/API/Protocol/ContentType.cs b/API/Protocol/ContentType.cs
index f8c30de5..9b1fb89c 100644
--- a/API/Protocol/ContentType.cs
+++ b/API/Protocol/ContentType.cs
@@ -30,6 +30,11 @@ public enum ContentType
///
ApplicationJson,
+ ///
+ /// A YAML file.
+ ///
+ ApplicationYaml,
+
///
/// A PNG image.
///
@@ -314,9 +319,9 @@ public enum ContentType
///
public class FlexibleContentType
{
- private static readonly ConcurrentDictionary _RawCache = new(StringComparer.InvariantCultureIgnoreCase);
+ private static readonly ConcurrentDictionary RawCache = new(StringComparer.InvariantCultureIgnoreCase);
- private static readonly Dictionary _KnownCache = new();
+ private static readonly Dictionary KnownCache = new();
#region Get-/Setters
@@ -339,70 +344,191 @@ public class FlexibleContentType
#region Mapping
- private static readonly Dictionary MAPPING = new()
+ private static readonly Dictionary Mapping = new()
{
- { ContentType.AudioMp4, "audio/mp4" },
- { ContentType.AudioOgg, "audio/ogg" },
- { ContentType.AudioMpeg, "audio/mpeg" },
- { ContentType.AudioWav, "audio/wav" },
- { ContentType.ApplicationJavaScript, "application/javascript" },
- { ContentType.ApplicationOfficeDocumentWordProcessing, "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
- { ContentType.ApplicationOfficeDocumentPresentation, "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
- { ContentType.ApplicationOfficeDocumentSlideshow, "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
- { ContentType.ApplicationOfficeDocumentSheet, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
- { ContentType.ApplicationForceDownload, "application/force-download" },
- { ContentType.ApplicationOctetStream, "application/octet-stream" },
- { ContentType.ApplicationJson, "application/json" },
- { ContentType.ApplicationWwwFormUrlEncoded, "application/x-www-form-urlencoded" },
- { ContentType.ApplicationProtobuf, "application/protobuf" },
- { ContentType.FontEmbeddedOpenTypeFont, "font/eot" },
- { ContentType.FontOpenTypeFont, "font/otf" },
- { ContentType.FontTrueTypeFont, "font/ttf" },
- { ContentType.FontWoff, "font/woff" },
- { ContentType.FontWoff2, "font/woff2" },
- { ContentType.ImageIcon, "image/x-icon" },
- { ContentType.ImageGif, "image/gif" },
- { ContentType.ImageJpg, "image/jpg" },
- { ContentType.ImagePng, "image/png" },
- { ContentType.ImageTiff, "image/tiff" },
- { ContentType.ImageBmp, "image/bmp" },
- { ContentType.ImageScalableVectorGraphics, "image/svg" },
- { ContentType.ImageScalableVectorGraphicsXml, "image/svg+xml" },
- { ContentType.ImageScalableVectorGraphicsCompressed, "image/svgz" },
- { ContentType.TextHtml, "text/html" },
- { ContentType.TextCss, "text/css" },
- { ContentType.TextCsv, "text/csv" },
- { ContentType.TextRichText, "text/richtext" },
- { ContentType.TextPlain, "text/plain" },
- { ContentType.TextJavaScript, "text/javascript" },
- { ContentType.TextXml, "text/xml" },
- { ContentType.Video3Gpp, "video/3gpp" },
- { ContentType.Video3Gpp2, "video/3gpp2" },
- { ContentType.VideoAV1, "video/av1" },
- { ContentType.VideoAvc, "video/av" },
- { ContentType.VideoDV, "video/dv" },
- { ContentType.VideoH261, "video/H261" },
- { ContentType.VideoH263, "video/H263" },
- { ContentType.VideoH264, "video/H264" },
- { ContentType.VideoH265, "video/H265" },
- { ContentType.VideoH266, "video/H266" },
- { ContentType.VideoMatroska, "video/matroska" },
- { ContentType.VideoMatroska3D, "video/matroska-3d" },
- { ContentType.VideoMJ2, "video/mj2" },
- { ContentType.VideoMP4, "video/mp4" },
- { ContentType.VideoMpeg, "video/mpeg" },
- { ContentType.VideoMpeg4Generic, "video/mpeg4-generic" },
- { ContentType.VideoMpv, "video/MPV" },
- { ContentType.VideoQuicktime, "video/quicktime" },
- { ContentType.VideoRaw, "video/raw" },
- { ContentType.VideoVC1, "video/vc1" },
- { ContentType.VideoVC2, "video/vc2" },
- { ContentType.VideoVP8, "video/VP8" },
- { ContentType.VideoVP9, "video/VP9" },
- { ContentType.VideoWebM, "video/webm" }
+ {
+ ContentType.AudioMp4, "audio/mp4"
+ },
+ {
+ ContentType.AudioOgg, "audio/ogg"
+ },
+ {
+ ContentType.AudioMpeg, "audio/mpeg"
+ },
+ {
+ ContentType.AudioWav, "audio/wav"
+ },
+ {
+ ContentType.ApplicationJavaScript, "application/javascript"
+ },
+ {
+ ContentType.ApplicationOfficeDocumentWordProcessing, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+ },
+ {
+ ContentType.ApplicationOfficeDocumentPresentation, "application/vnd.openxmlformats-officedocument.presentationml.presentation"
+ },
+ {
+ ContentType.ApplicationOfficeDocumentSlideshow, "application/vnd.openxmlformats-officedocument.presentationml.slideshow"
+ },
+ {
+ ContentType.ApplicationOfficeDocumentSheet, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
+ },
+ {
+ ContentType.ApplicationForceDownload, "application/force-download"
+ },
+ {
+ ContentType.ApplicationOctetStream, "application/octet-stream"
+ },
+ {
+ ContentType.ApplicationJson, "application/json"
+ },
+ {
+ ContentType.ApplicationYaml, "application/yaml"
+ },
+ {
+ ContentType.ApplicationWwwFormUrlEncoded, "application/x-www-form-urlencoded"
+ },
+ {
+ ContentType.ApplicationProtobuf, "application/protobuf"
+ },
+ {
+ ContentType.FontEmbeddedOpenTypeFont, "font/eot"
+ },
+ {
+ ContentType.FontOpenTypeFont, "font/otf"
+ },
+ {
+ ContentType.FontTrueTypeFont, "font/ttf"
+ },
+ {
+ ContentType.FontWoff, "font/woff"
+ },
+ {
+ ContentType.FontWoff2, "font/woff2"
+ },
+ {
+ ContentType.ImageIcon, "image/x-icon"
+ },
+ {
+ ContentType.ImageGif, "image/gif"
+ },
+ {
+ ContentType.ImageJpg, "image/jpg"
+ },
+ {
+ ContentType.ImagePng, "image/png"
+ },
+ {
+ ContentType.ImageTiff, "image/tiff"
+ },
+ {
+ ContentType.ImageBmp, "image/bmp"
+ },
+ {
+ ContentType.ImageScalableVectorGraphics, "image/svg"
+ },
+ {
+ ContentType.ImageScalableVectorGraphicsXml, "image/svg+xml"
+ },
+ {
+ ContentType.ImageScalableVectorGraphicsCompressed, "image/svgz"
+ },
+ {
+ ContentType.TextHtml, "text/html"
+ },
+ {
+ ContentType.TextCss, "text/css"
+ },
+ {
+ ContentType.TextCsv, "text/csv"
+ },
+ {
+ ContentType.TextRichText, "text/richtext"
+ },
+ {
+ ContentType.TextPlain, "text/plain"
+ },
+ {
+ ContentType.TextJavaScript, "text/javascript"
+ },
+ {
+ ContentType.TextXml, "text/xml"
+ },
+ {
+ ContentType.Video3Gpp, "video/3gpp"
+ },
+ {
+ ContentType.Video3Gpp2, "video/3gpp2"
+ },
+ {
+ ContentType.VideoAV1, "video/av1"
+ },
+ {
+ ContentType.VideoAvc, "video/av"
+ },
+ {
+ ContentType.VideoDV, "video/dv"
+ },
+ {
+ ContentType.VideoH261, "video/H261"
+ },
+ {
+ ContentType.VideoH263, "video/H263"
+ },
+ {
+ ContentType.VideoH264, "video/H264"
+ },
+ {
+ ContentType.VideoH265, "video/H265"
+ },
+ {
+ ContentType.VideoH266, "video/H266"
+ },
+ {
+ ContentType.VideoMatroska, "video/matroska"
+ },
+ {
+ ContentType.VideoMatroska3D, "video/matroska-3d"
+ },
+ {
+ ContentType.VideoMJ2, "video/mj2"
+ },
+ {
+ ContentType.VideoMP4, "video/mp4"
+ },
+ {
+ ContentType.VideoMpeg, "video/mpeg"
+ },
+ {
+ ContentType.VideoMpeg4Generic, "video/mpeg4-generic"
+ },
+ {
+ ContentType.VideoMpv, "video/MPV"
+ },
+ {
+ ContentType.VideoQuicktime, "video/quicktime"
+ },
+ {
+ ContentType.VideoRaw, "video/raw"
+ },
+ {
+ ContentType.VideoVC1, "video/vc1"
+ },
+ {
+ ContentType.VideoVC2, "video/vc2"
+ },
+ {
+ ContentType.VideoVP8, "video/VP8"
+ },
+ {
+ ContentType.VideoVP9, "video/VP9"
+ },
+ {
+ ContentType.VideoWebM, "video/webm"
+ }
};
- private static readonly Dictionary MAPPING_REVERSE = MAPPING.ToDictionary(x => x.Value, x => x.Key);
+ private static readonly Dictionary MAPPING_REVERSE = Mapping.ToDictionary(x => x.Value, x => x.Key);
#endregion
@@ -436,7 +562,7 @@ public FlexibleContentType(string rawType, string? charset = null)
public FlexibleContentType(ContentType type, string? charset = null)
{
KnownType = type;
- RawType = MAPPING[type];
+ RawType = Mapping[type];
Charset = charset;
}
@@ -457,14 +583,14 @@ public static FlexibleContentType Get(string rawType, string? charset = null)
return new FlexibleContentType(rawType, charset);
}
- if (_RawCache.TryGetValue(rawType, out var found))
+ if (RawCache.TryGetValue(rawType, out var found))
{
return found;
}
var type = new FlexibleContentType(rawType);
- _RawCache[rawType] = type;
+ RawCache[rawType] = type;
return type;
}
@@ -481,14 +607,14 @@ public static FlexibleContentType Get(ContentType knownType, string? charset = n
return new FlexibleContentType(knownType, charset);
}
- if (_KnownCache.TryGetValue(knownType, out var found))
+ if (KnownCache.TryGetValue(knownType, out var found))
{
return found;
}
var type = new FlexibleContentType(knownType);
- _KnownCache[knownType] = type;
+ KnownCache[knownType] = type;
return type;
}
diff --git a/API/Protocol/IRequestProperties.cs b/API/Protocol/IRequestProperties.cs
index d8cf3f66..e4874a4c 100644
--- a/API/Protocol/IRequestProperties.cs
+++ b/API/Protocol/IRequestProperties.cs
@@ -31,5 +31,4 @@ public interface IRequestProperties : IDisposable
///
/// The entry to be removed
void Clear(string key);
-
}
diff --git a/API/Protocol/RequestMethod.cs b/API/Protocol/RequestMethod.cs
index 6a631c1e..9d387663 100644
--- a/API/Protocol/RequestMethod.cs
+++ b/API/Protocol/RequestMethod.cs
@@ -23,44 +23,96 @@ public enum RequestMethod
///
public class FlexibleRequestMethod
{
- private static readonly Dictionary _RawCache = new(StringComparer.InvariantCultureIgnoreCase)
+ private static readonly Dictionary RawCache = new(StringComparer.InvariantCultureIgnoreCase)
{
- { "HEAD", new FlexibleRequestMethod(RequestMethod.Head) },
- { "GET", new FlexibleRequestMethod(RequestMethod.Get) },
- { "POST", new FlexibleRequestMethod(RequestMethod.Post) },
- { "PUT", new FlexibleRequestMethod(RequestMethod.Put) },
- { "DELETE", new FlexibleRequestMethod(RequestMethod.Delete) },
- { "OPTIONS", new FlexibleRequestMethod(RequestMethod.Options) }
+ {
+ "HEAD", new FlexibleRequestMethod(RequestMethod.Head)
+ },
+ {
+ "GET", new FlexibleRequestMethod(RequestMethod.Get)
+ },
+ {
+ "POST", new FlexibleRequestMethod(RequestMethod.Post)
+ },
+ {
+ "PUT", new FlexibleRequestMethod(RequestMethod.Put)
+ },
+ {
+ "DELETE", new FlexibleRequestMethod(RequestMethod.Delete)
+ },
+ {
+ "OPTIONS", new FlexibleRequestMethod(RequestMethod.Options)
+ }
};
- private static readonly Dictionary _KnownCache = new()
+ private static readonly Dictionary KnownCache = new()
{
- { RequestMethod.Head, new FlexibleRequestMethod(RequestMethod.Head) },
- { RequestMethod.Get, new FlexibleRequestMethod(RequestMethod.Get) },
- { RequestMethod.Post, new FlexibleRequestMethod(RequestMethod.Post) },
- { RequestMethod.Put, new FlexibleRequestMethod(RequestMethod.Put) },
- { RequestMethod.Delete, new FlexibleRequestMethod(RequestMethod.Delete) },
- { RequestMethod.Options, new FlexibleRequestMethod(RequestMethod.Options) }
+ {
+ RequestMethod.Head, new FlexibleRequestMethod(RequestMethod.Head)
+ },
+ {
+ RequestMethod.Get, new FlexibleRequestMethod(RequestMethod.Get)
+ },
+ {
+ RequestMethod.Post, new FlexibleRequestMethod(RequestMethod.Post)
+ },
+ {
+ RequestMethod.Put, new FlexibleRequestMethod(RequestMethod.Put)
+ },
+ {
+ RequestMethod.Delete, new FlexibleRequestMethod(RequestMethod.Delete)
+ },
+ {
+ RequestMethod.Options, new FlexibleRequestMethod(RequestMethod.Options)
+ }
};
#region Mapping
- private static readonly Dictionary MAPPING = new(StringComparer.OrdinalIgnoreCase)
+ private static readonly Dictionary Mapping = new(StringComparer.OrdinalIgnoreCase)
{
- { "GET", RequestMethod.Get },
- { "HEAD", RequestMethod.Head },
- { "POST", RequestMethod.Post },
- { "PUT", RequestMethod.Put },
- { "PATCH", RequestMethod.Patch },
- { "DELETE", RequestMethod.Delete },
- { "OPTIONS", RequestMethod.Options },
- { "PROPFIND", RequestMethod.PropFind },
- { "PROPPATCH", RequestMethod.PropPatch },
- { "MKCOL", RequestMethod.MkCol },
- { "COPY", RequestMethod.Copy },
- { "MOVE", RequestMethod.Move },
- { "LOCK", RequestMethod.Lock },
- { "UNLOCK", RequestMethod.Unlock }
+ {
+ "GET", RequestMethod.Get
+ },
+ {
+ "HEAD", RequestMethod.Head
+ },
+ {
+ "POST", RequestMethod.Post
+ },
+ {
+ "PUT", RequestMethod.Put
+ },
+ {
+ "PATCH", RequestMethod.Patch
+ },
+ {
+ "DELETE", RequestMethod.Delete
+ },
+ {
+ "OPTIONS", RequestMethod.Options
+ },
+ {
+ "PROPFIND", RequestMethod.PropFind
+ },
+ {
+ "PROPPATCH", RequestMethod.PropPatch
+ },
+ {
+ "MKCOL", RequestMethod.MkCol
+ },
+ {
+ "COPY", RequestMethod.Copy
+ },
+ {
+ "MOVE", RequestMethod.Move
+ },
+ {
+ "LOCK", RequestMethod.Lock
+ },
+ {
+ "UNLOCK", RequestMethod.Unlock
+ }
};
#endregion
@@ -99,7 +151,7 @@ public FlexibleRequestMethod(string rawType)
{
RawMethod = rawType;
- if (MAPPING.TryGetValue(rawType, out var type))
+ if (Mapping.TryGetValue(rawType, out var type))
{
KnownMethod = type;
}
@@ -120,14 +172,14 @@ public FlexibleRequestMethod(string rawType)
/// The content type instance to be used
public static FlexibleRequestMethod Get(string rawMethod)
{
- if (_RawCache.TryGetValue(rawMethod, out var found))
+ if (RawCache.TryGetValue(rawMethod, out var found))
{
return found;
}
var method = new FlexibleRequestMethod(rawMethod);
- _RawCache[rawMethod] = method;
+ RawCache[rawMethod] = method;
return method;
}
@@ -139,14 +191,14 @@ public static FlexibleRequestMethod Get(string rawMethod)
/// The content type instance to be used
public static FlexibleRequestMethod Get(RequestMethod knownMethod)
{
- if (_KnownCache.TryGetValue(knownMethod, out var found))
+ if (KnownCache.TryGetValue(knownMethod, out var found))
{
return found;
}
var method = new FlexibleRequestMethod(knownMethod);
- _KnownCache[knownMethod] = method;
+ KnownCache[knownMethod] = method;
return method;
}
diff --git a/API/Protocol/ResponseStatus.cs b/API/Protocol/ResponseStatus.cs
index bf3e3dfb..40d76ec6 100644
--- a/API/Protocol/ResponseStatus.cs
+++ b/API/Protocol/ResponseStatus.cs
@@ -140,57 +140,159 @@ public readonly struct FlexibleResponseStatus
private static readonly Dictionary MAPPING = new()
{
- { ResponseStatus.Accepted, "Accepted" },
- { ResponseStatus.BadGateway, "Bad Gateway" },
- { ResponseStatus.BadRequest, "Bad Request" },
- { ResponseStatus.Created, "Created" },
- { ResponseStatus.Forbidden, "Forbidden" },
- { ResponseStatus.InternalServerError, "Internal Server Error" },
- { ResponseStatus.MethodNotAllowed, "Method Not Allowed" },
- { ResponseStatus.MovedPermanently, "Moved Permamently" },
- { ResponseStatus.Found, "Found" },
- { ResponseStatus.NoContent, "No Content" },
- { ResponseStatus.NotFound, "Not Found" },
- { ResponseStatus.NotImplemented, "Not Implemented" },
- { ResponseStatus.NotModified, "Not Modified" },
- { ResponseStatus.Ok, "OK" },
- { ResponseStatus.ServiceUnavailable, "Service Unavailable" },
- { ResponseStatus.Unauthorized, "Unauthorized" },
- { ResponseStatus.PartialContent, "Partial Content" },
- { ResponseStatus.MultiStatus, "Multi-Status" },
- { ResponseStatus.AlreadyReported, "Already Reported" },
- { ResponseStatus.SeeOther, "See Other" },
- { ResponseStatus.TemporaryRedirect, "Temporary Redirect" },
- { ResponseStatus.PermanentRedirect, "Permanent Redirect" },
- { ResponseStatus.Continue, "Continue" },
- { ResponseStatus.SwitchingProtocols, "Switching Protocols" },
- { ResponseStatus.NotAcceptable, "Not Acceptable" },
- { ResponseStatus.ProxyAuthenticationRequired, "Proxy Authentication Required" },
- { ResponseStatus.Conflict, "Conflict" },
- { ResponseStatus.Gone, "Gone" },
- { ResponseStatus.LengthRequired, "Length Required" },
- { ResponseStatus.PreconditionFailed, "Precondition Failed" },
- { ResponseStatus.RequestEntityTooLarge, "Request Entity Too Large" },
- { ResponseStatus.RequestUriTooLong, "Request Uri Too Long" },
- { ResponseStatus.UnsupportedMediaType, "Unsupported Media Type" },
- { ResponseStatus.RequestedRangeNotSatisfiable, "Requested Range Not Satisfiable" },
- { ResponseStatus.ExpectationFailed, "Expectation Failed" },
- { ResponseStatus.UnprocessableEntity, "Unprocessable Entity" },
- { ResponseStatus.Locked, "Locked" },
- { ResponseStatus.FailedDependency, "Failed Dependency" },
- { ResponseStatus.ReservedForWebDav, "Reserved For WebDAV" },
- { ResponseStatus.UpgradeRequired, "Upgrade Required" },
- { ResponseStatus.PreconditionRequired, "Precondition Required" },
- { ResponseStatus.TooManyRequests, "Too Many Requests" },
- { ResponseStatus.RequestHeaderFieldsTooLarge, "Request Header Fields Too Large" },
- { ResponseStatus.UnavailableForLegalReasons, "Unavailable For Legal Reasons" },
- { ResponseStatus.GatewayTimeout, "Gateway Timeout" },
- { ResponseStatus.HttpVersionNotSupported, "HTTP Version Not Supported" },
- { ResponseStatus.InsufficientStorage, "Insufficient Storage" },
- { ResponseStatus.LoopDetected, "Loop Detected" },
- { ResponseStatus.NotExtended, "Not Extended" },
- { ResponseStatus.NetworkAuthenticationRequired, "Network Authentication Required" },
- { ResponseStatus.Processing, "Processing" }
+ {
+ ResponseStatus.Accepted, "Accepted"
+ },
+ {
+ ResponseStatus.BadGateway, "Bad Gateway"
+ },
+ {
+ ResponseStatus.BadRequest, "Bad Request"
+ },
+ {
+ ResponseStatus.Created, "Created"
+ },
+ {
+ ResponseStatus.Forbidden, "Forbidden"
+ },
+ {
+ ResponseStatus.InternalServerError, "Internal Server Error"
+ },
+ {
+ ResponseStatus.MethodNotAllowed, "Method Not Allowed"
+ },
+ {
+ ResponseStatus.MovedPermanently, "Moved Permamently"
+ },
+ {
+ ResponseStatus.Found, "Found"
+ },
+ {
+ ResponseStatus.NoContent, "No Content"
+ },
+ {
+ ResponseStatus.NotFound, "Not Found"
+ },
+ {
+ ResponseStatus.NotImplemented, "Not Implemented"
+ },
+ {
+ ResponseStatus.NotModified, "Not Modified"
+ },
+ {
+ ResponseStatus.Ok, "OK"
+ },
+ {
+ ResponseStatus.ServiceUnavailable, "Service Unavailable"
+ },
+ {
+ ResponseStatus.Unauthorized, "Unauthorized"
+ },
+ {
+ ResponseStatus.PartialContent, "Partial Content"
+ },
+ {
+ ResponseStatus.MultiStatus, "Multi-Status"
+ },
+ {
+ ResponseStatus.AlreadyReported, "Already Reported"
+ },
+ {
+ ResponseStatus.SeeOther, "See Other"
+ },
+ {
+ ResponseStatus.TemporaryRedirect, "Temporary Redirect"
+ },
+ {
+ ResponseStatus.PermanentRedirect, "Permanent Redirect"
+ },
+ {
+ ResponseStatus.Continue, "Continue"
+ },
+ {
+ ResponseStatus.SwitchingProtocols, "Switching Protocols"
+ },
+ {
+ ResponseStatus.NotAcceptable, "Not Acceptable"
+ },
+ {
+ ResponseStatus.ProxyAuthenticationRequired, "Proxy Authentication Required"
+ },
+ {
+ ResponseStatus.Conflict, "Conflict"
+ },
+ {
+ ResponseStatus.Gone, "Gone"
+ },
+ {
+ ResponseStatus.LengthRequired, "Length Required"
+ },
+ {
+ ResponseStatus.PreconditionFailed, "Precondition Failed"
+ },
+ {
+ ResponseStatus.RequestEntityTooLarge, "Request Entity Too Large"
+ },
+ {
+ ResponseStatus.RequestUriTooLong, "Request Uri Too Long"
+ },
+ {
+ ResponseStatus.UnsupportedMediaType, "Unsupported Media Type"
+ },
+ {
+ ResponseStatus.RequestedRangeNotSatisfiable, "Requested Range Not Satisfiable"
+ },
+ {
+ ResponseStatus.ExpectationFailed, "Expectation Failed"
+ },
+ {
+ ResponseStatus.UnprocessableEntity, "Unprocessable Entity"
+ },
+ {
+ ResponseStatus.Locked, "Locked"
+ },
+ {
+ ResponseStatus.FailedDependency, "Failed Dependency"
+ },
+ {
+ ResponseStatus.ReservedForWebDav, "Reserved For WebDAV"
+ },
+ {
+ ResponseStatus.UpgradeRequired, "Upgrade Required"
+ },
+ {
+ ResponseStatus.PreconditionRequired, "Precondition Required"
+ },
+ {
+ ResponseStatus.TooManyRequests, "Too Many Requests"
+ },
+ {
+ ResponseStatus.RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"
+ },
+ {
+ ResponseStatus.UnavailableForLegalReasons, "Unavailable For Legal Reasons"
+ },
+ {
+ ResponseStatus.GatewayTimeout, "Gateway Timeout"
+ },
+ {
+ ResponseStatus.HttpVersionNotSupported, "HTTP Version Not Supported"
+ },
+ {
+ ResponseStatus.InsufficientStorage, "Insufficient Storage"
+ },
+ {
+ ResponseStatus.LoopDetected, "Loop Detected"
+ },
+ {
+ ResponseStatus.NotExtended, "Not Extended"
+ },
+ {
+ ResponseStatus.NetworkAuthenticationRequired, "Network Authentication Required"
+ },
+ {
+ ResponseStatus.Processing, "Processing"
+ }
};
private static readonly Dictionary CODE_MAPPING = MAPPING.Keys.ToDictionary(k => (int)k, k => k);
diff --git a/Engine/Infrastructure/Endpoints/SecureEndPoint.cs b/Engine/Infrastructure/Endpoints/SecureEndPoint.cs
index 3a0494b2..82f024dc 100644
--- a/Engine/Infrastructure/Endpoints/SecureEndPoint.cs
+++ b/Engine/Infrastructure/Endpoints/SecureEndPoint.cs
@@ -25,7 +25,9 @@ internal SecureEndPoint(IServer server, IPEndPoint endPoint, SecurityConfigurati
ClientCertificateRequired = false,
AllowRenegotiation = true,
ApplicationProtocols = new List
- { SslApplicationProtocol.Http11 },
+ {
+ SslApplicationProtocol.Http11
+ },
CertificateRevocationCheckMode = X509RevocationMode.NoCheck, // no support for client certificates yet
EncryptionPolicy = EncryptionPolicy.RequireEncryption,
ServerCertificateSelectionCallback = SelectCertificate
diff --git a/Engine/Infrastructure/ThreadedServerBuilder.cs b/Engine/Infrastructure/ThreadedServerBuilder.cs
index dbb40e29..8cf94e5b 100644
--- a/Engine/Infrastructure/ThreadedServerBuilder.cs
+++ b/Engine/Infrastructure/ThreadedServerBuilder.cs
@@ -10,18 +10,18 @@ namespace GenHTTP.Engine.Infrastructure;
internal sealed class ThreadedServerBuilder : IServerBuilder
{
+
+ private readonly List _Concerns = [];
private readonly List _EndPoints = [];
+ private ushort _Backlog = 1024;
private IServerCompanion? _Companion;
- private IHandlerBuilder? _Handler;
-
- private readonly List _Concerns = [];
-
private bool _Development;
+ private IHandlerBuilder? _Handler;
+
private ushort _Port = 8080;
- private ushort _Backlog = 1024;
private uint _RequestMemoryLimit = 1 * 1024 * 1024; // 1 MB
@@ -69,7 +69,10 @@ public IServer Build()
var config = new ServerConfiguration(_Development, endpoints, network);
- var concerns = new[] { ErrorHandler.Default() }.Concat(_Concerns);
+ var concerns = new[]
+ {
+ ErrorHandler.Default()
+ }.Concat(_Concerns);
var handler = new CoreRouter(_Handler, concerns);
diff --git a/Engine/Protocol/Parser/Conversion/MethodConverter.cs b/Engine/Protocol/Parser/Conversion/MethodConverter.cs
index d3cc6e80..7a51195f 100644
--- a/Engine/Protocol/Parser/Conversion/MethodConverter.cs
+++ b/Engine/Protocol/Parser/Conversion/MethodConverter.cs
@@ -7,13 +7,27 @@ internal static class MethodConverter
{
private static readonly Dictionary KnownMethods = new(7)
{
- { "GET", RequestMethod.Get },
- { "HEAD", RequestMethod.Head },
- { "POST", RequestMethod.Post },
- { "PUT", RequestMethod.Put },
- { "PATCH", RequestMethod.Patch },
- { "DELETE", RequestMethod.Delete },
- { "OPTIONS", RequestMethod.Options }
+ {
+ "GET", RequestMethod.Get
+ },
+ {
+ "HEAD", RequestMethod.Head
+ },
+ {
+ "POST", RequestMethod.Post
+ },
+ {
+ "PUT", RequestMethod.Put
+ },
+ {
+ "PATCH", RequestMethod.Patch
+ },
+ {
+ "DELETE", RequestMethod.Delete
+ },
+ {
+ "OPTIONS", RequestMethod.Options
+ }
};
internal static FlexibleRequestMethod ToRequestMethod(ReadOnlySequence value)
diff --git a/Engine/Protocol/RequestQuery.cs b/Engine/Protocol/RequestQuery.cs
index d49d72e2..118f09ba 100644
--- a/Engine/Protocol/RequestQuery.cs
+++ b/Engine/Protocol/RequestQuery.cs
@@ -11,5 +11,4 @@ internal RequestQuery() : base(DefaultSize, StringComparer.OrdinalIgnoreCase)
{
}
-
}
diff --git a/Engine/Protocol/Response.cs b/Engine/Protocol/Response.cs
index f41c6d07..13ce31df 100644
--- a/Engine/Protocol/Response.cs
+++ b/Engine/Protocol/Response.cs
@@ -59,7 +59,10 @@ public string? this[string field]
{
_Headers[field] = value;
}
- else _Headers.Remove(field);
+ else
+ {
+ _Headers.Remove(field);
+ }
}
}
diff --git a/Engine/Protocol/ResponseHeaderCollection.cs b/Engine/Protocol/ResponseHeaderCollection.cs
index 2d4aff11..f24e0020 100644
--- a/Engine/Protocol/ResponseHeaderCollection.cs
+++ b/Engine/Protocol/ResponseHeaderCollection.cs
@@ -9,8 +9,14 @@ internal sealed class ResponseHeaderCollection : PooledDictionary ReservedHeaders = new(StringComparer.InvariantCultureIgnoreCase)
{
- "Date", "Connection", "Content-Type", "Content-Encoding", "Content-Length",
- "Transfer-Encoding", "Last-Modified", "Expires"
+ "Date",
+ "Connection",
+ "Content-Type",
+ "Content-Encoding",
+ "Content-Length",
+ "Transfer-Encoding",
+ "Last-Modified",
+ "Expires"
};
#region Initialization
diff --git a/Engine/Utilities/PooledDictionary.cs b/Engine/Utilities/PooledDictionary.cs
index ae5ecd9d..a5204009 100644
--- a/Engine/Utilities/PooledDictionary.cs
+++ b/Engine/Utilities/PooledDictionary.cs
@@ -263,7 +263,7 @@ private void CheckResize()
#region IDisposable Support
- private bool _Disposed = false;
+ private bool _Disposed;
private void Dispose(bool disposing)
{
diff --git a/GenHTTP.sln b/GenHTTP.sln
index 6aeda4da..dcae9f04 100644
--- a/GenHTTP.sln
+++ b/GenHTTP.sln
@@ -88,6 +88,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenHTTP.Testing", "Testing\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenHTTP.Modules.Pages", "Modules\Pages\GenHTTP.Modules.Pages.csproj", "{4CDA31EB-A6C2-4634-9379-9306D3996B21}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenHTTP.Modules.OpenApi", "Modules\OpenApi\GenHTTP.Modules.OpenApi.csproj", "{A5149821-D510-4854-9DC9-D489323BC545}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -210,6 +212,10 @@ Global
{4CDA31EB-A6C2-4634-9379-9306D3996B21}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4CDA31EB-A6C2-4634-9379-9306D3996B21}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4CDA31EB-A6C2-4634-9379-9306D3996B21}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5149821-D510-4854-9DC9-D489323BC545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5149821-D510-4854-9DC9-D489323BC545}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5149821-D510-4854-9DC9-D489323BC545}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A5149821-D510-4854-9DC9-D489323BC545}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -246,9 +252,10 @@ Global
{C5067243-AFBA-4A17-BD61-DDB503367EA3} = {A7930BE4-0549-4197-B139-B1A73E74B464}
{FC7F7D69-5ED0-4D3B-B201-EDBCEC71B8DB} = {A7930BE4-0549-4197-B139-B1A73E74B464}
{4CDA31EB-A6C2-4634-9379-9306D3996B21} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
+ {A5149821-D510-4854-9DC9-D489323BC545} = {23B23225-275E-4F52-8B29-6F44C85B6ACE}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- LessCompiler = 2603124e-1287-4d61-9540-6ac3efad4eb9
SolutionGuid = {9C67B3AF-0BF6-4E21-8C39-3F74CFCF9632}
+ LessCompiler = 2603124e-1287-4d61-9540-6ac3efad4eb9
EndGlobalSection
EndGlobal
diff --git a/Modules/Authentication/BasicAuthentication.cs b/Modules/Authentication/BasicAuthentication.cs
index 046bf06f..afe4e830 100644
--- a/Modules/Authentication/BasicAuthentication.cs
+++ b/Modules/Authentication/BasicAuthentication.cs
@@ -23,7 +23,7 @@ public static class BasicAuthentication
/// The name of the realm returned to the client
/// The newly created basic authentication concern
public static BasicAuthenticationConcernBuilder Create(Func> authenticator, string realm = DefaultRealm) => new BasicAuthenticationConcernBuilder().Handler(authenticator)
- .Realm(realm);
+ .Realm(realm);
///
/// Creates a basic authentication concern that stores credentials in
diff --git a/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs b/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs
index 4a8d61e5..21e0e504 100644
--- a/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs
+++ b/Modules/Authentication/Bearer/BearerAuthenticationConcern.cs
@@ -15,7 +15,6 @@ internal class OpenIDConfiguration
[JsonPropertyName("jwks_uri")]
public string? KeySetUrl { get; init; }
-
}
#endregion
diff --git a/Modules/Authentication/Extensions.cs b/Modules/Authentication/Extensions.cs
index d4f77010..4bea3cd6 100644
--- a/Modules/Authentication/Extensions.cs
+++ b/Modules/Authentication/Extensions.cs
@@ -12,14 +12,10 @@ public static void SetUser(this IRequest request, IUser user)
request.Properties[UserProperty] = user;
}
- public static T? GetUser(this IRequest request) where T : class, IUser
- {
- return request.Properties.TryGet(UserProperty, out var user) ? user : null;
- }
+ public static T? GetUser(this IRequest request) where T : class, IUser => request.Properties.TryGet(UserProperty, out var user) ? user : null;
public static void ClearUser(this IRequest request)
{
request.Properties.Clear(UserProperty);
}
-
}
diff --git a/Modules/Basics/CoreExtensions.cs b/Modules/Basics/CoreExtensions.cs
index 35863319..93f69d78 100644
--- a/Modules/Basics/CoreExtensions.cs
+++ b/Modules/Basics/CoreExtensions.cs
@@ -69,66 +69,168 @@ public static bool HasType(this IRequest request, params RequestMethod[] methods
private static readonly Dictionary ContentTypes = new()
{
// CSS
- { "css", ContentType.TextCss },
+ {
+ "css", ContentType.TextCss
+ },
// HTML
- { "html", ContentType.TextHtml },
- { "htm", ContentType.TextHtml },
+ {
+ "html", ContentType.TextHtml
+ },
+ {
+ "htm", ContentType.TextHtml
+ },
// Text files
- { "txt", ContentType.TextPlain },
- { "conf", ContentType.TextPlain },
- { "config", ContentType.TextPlain },
+ {
+ "txt", ContentType.TextPlain
+ },
+ {
+ "conf", ContentType.TextPlain
+ },
+ {
+ "config", ContentType.TextPlain
+ },
// Fonts
- { "eot", ContentType.FontEmbeddedOpenTypeFont },
- { "ttf", ContentType.FontTrueTypeFont },
- { "otf", ContentType.FontOpenTypeFont },
- { "woff", ContentType.FontWoff },
- { "woff2", ContentType.FontWoff2 },
+ {
+ "eot", ContentType.FontEmbeddedOpenTypeFont
+ },
+ {
+ "ttf", ContentType.FontTrueTypeFont
+ },
+ {
+ "otf", ContentType.FontOpenTypeFont
+ },
+ {
+ "woff", ContentType.FontWoff
+ },
+ {
+ "woff2", ContentType.FontWoff2
+ },
// Scripts
- { "js", ContentType.ApplicationJavaScript },
- { "mjs", ContentType.ApplicationJavaScript },
+ {
+ "js", ContentType.ApplicationJavaScript
+ },
+ {
+ "mjs", ContentType.ApplicationJavaScript
+ },
// Images
- { "ico", ContentType.ImageIcon },
- { "gif", ContentType.ImageGif },
- { "jpeg", ContentType.ImageJpg },
- { "jpg", ContentType.ImageJpg },
- { "png", ContentType.ImagePng },
- { "bmp", ContentType.ImageBmp },
- { "tiff", ContentType.ImageTiff },
- { "svg", ContentType.ImageScalableVectorGraphicsXml },
- { "svgz", ContentType.ImageScalableVectorGraphicsCompressed },
+ {
+ "ico", ContentType.ImageIcon
+ },
+ {
+ "gif", ContentType.ImageGif
+ },
+ {
+ "jpeg", ContentType.ImageJpg
+ },
+ {
+ "jpg", ContentType.ImageJpg
+ },
+ {
+ "png", ContentType.ImagePng
+ },
+ {
+ "bmp", ContentType.ImageBmp
+ },
+ {
+ "tiff", ContentType.ImageTiff
+ },
+ {
+ "svg", ContentType.ImageScalableVectorGraphicsXml
+ },
+ {
+ "svgz", ContentType.ImageScalableVectorGraphicsCompressed
+ },
// Audio
- { "ogg", ContentType.AudioOgg },
- { "mp3", ContentType.AudioMpeg },
- { "wav", ContentType.AudioWav },
+ {
+ "ogg", ContentType.AudioOgg
+ },
+ {
+ "mp3", ContentType.AudioMpeg
+ },
+ {
+ "wav", ContentType.AudioWav
+ },
// Video
- { "avi", ContentType.VideoMpeg },
- { "3gp", ContentType.Video3Gpp },
- { "3g2", ContentType.Video3Gpp2 },
- { "av1", ContentType.VideoAV1 },
- { "avc", ContentType.VideoAvc },
- { "dv", ContentType.VideoDV },
- { "mkv", ContentType.VideoMatroska },
- { "mk3d", ContentType.VideoMatroska3D },
- { "mj2", ContentType.VideoMJ2 },
- { "mpg", ContentType.VideoMpeg },
- { "mp4", ContentType.VideoMP4 },
- { "mpeg", ContentType.VideoMpeg },
- { "mpv", ContentType.VideoMpv },
- { "mov", ContentType.VideoQuicktime },
- { "hdmov", ContentType.VideoQuicktime },
- { "vc1", ContentType.VideoVC1 },
- { "vc2", ContentType.VideoVC2 },
- { "webm", ContentType.VideoWebM },
+ {
+ "avi", ContentType.VideoMpeg
+ },
+ {
+ "3gp", ContentType.Video3Gpp
+ },
+ {
+ "3g2", ContentType.Video3Gpp2
+ },
+ {
+ "av1", ContentType.VideoAV1
+ },
+ {
+ "avc", ContentType.VideoAvc
+ },
+ {
+ "dv", ContentType.VideoDV
+ },
+ {
+ "mkv", ContentType.VideoMatroska
+ },
+ {
+ "mk3d", ContentType.VideoMatroska3D
+ },
+ {
+ "mj2", ContentType.VideoMJ2
+ },
+ {
+ "mpg", ContentType.VideoMpeg
+ },
+ {
+ "mp4", ContentType.VideoMP4
+ },
+ {
+ "mpeg", ContentType.VideoMpeg
+ },
+ {
+ "mpv", ContentType.VideoMpv
+ },
+ {
+ "mov", ContentType.VideoQuicktime
+ },
+ {
+ "hdmov", ContentType.VideoQuicktime
+ },
+ {
+ "vc1", ContentType.VideoVC1
+ },
+ {
+ "vc2", ContentType.VideoVC2
+ },
+ {
+ "webm", ContentType.VideoWebM
+ },
// Documents
- { "csv", ContentType.TextCsv },
- { "rtf", ContentType.TextRichText },
- { "docx", ContentType.ApplicationOfficeDocumentWordProcessing },
- { "pptx", ContentType.ApplicationOfficeDocumentPresentation },
- { "ppsx", ContentType.ApplicationOfficeDocumentSlideshow },
- { "xslx", ContentType.ApplicationOfficeDocumentSheet },
+ {
+ "csv", ContentType.TextCsv
+ },
+ {
+ "rtf", ContentType.TextRichText
+ },
+ {
+ "docx", ContentType.ApplicationOfficeDocumentWordProcessing
+ },
+ {
+ "pptx", ContentType.ApplicationOfficeDocumentPresentation
+ },
+ {
+ "ppsx", ContentType.ApplicationOfficeDocumentSlideshow
+ },
+ {
+ "xslx", ContentType.ApplicationOfficeDocumentSheet
+ },
// Object models
- { "json", ContentType.ApplicationJson },
- { "xml", ContentType.TextXml }
+ {
+ "json", ContentType.ApplicationJson
+ },
+ {
+ "xml", ContentType.TextXml
+ }
};
public static ContentType? GuessContentType(this string fileName)
diff --git a/Modules/Compression/Providers/CompressionConcern.cs b/Modules/Compression/Providers/CompressionConcern.cs
index cc50f9ef..50489b56 100644
--- a/Modules/Compression/Providers/CompressionConcern.cs
+++ b/Modules/Compression/Providers/CompressionConcern.cs
@@ -110,7 +110,7 @@ private static bool ShouldCompress(WebPath path, ContentType? type)
case ContentType.ImageBmp:
case ContentType.TextXml:
case ContentType.TextJavaScript:
-
+ case ContentType.ApplicationYaml:
{
return true;
}
diff --git a/Modules/Controllers/Controller.cs b/Modules/Controllers/Controller.cs
index 8c95270b..f2a33a2b 100644
--- a/Modules/Controllers/Controller.cs
+++ b/Modules/Controllers/Controller.cs
@@ -19,5 +19,4 @@ public static class Controller
/// The instance to be used
/// The newly created request handler
public static ControllerBuilder From(object instance) => new ControllerBuilder().Instance(instance);
-
}
diff --git a/Modules/Controllers/ControllerActionAttribute.cs b/Modules/Controllers/ControllerActionAttribute.cs
index c1aeceb1..9f8661b7 100644
--- a/Modules/Controllers/ControllerActionAttribute.cs
+++ b/Modules/Controllers/ControllerActionAttribute.cs
@@ -17,5 +17,4 @@ public ControllerActionAttribute(params RequestMethod[] methods) : base(methods)
///
/// The request methods which are supported by this action
public ControllerActionAttribute(params FlexibleRequestMethod[] methods) : base(methods) { }
-
}
diff --git a/Modules/Controllers/Provider/ControllerBuilder.cs b/Modules/Controllers/Provider/ControllerBuilder.cs
index 385c3f4f..e15aa912 100644
--- a/Modules/Controllers/Provider/ControllerBuilder.cs
+++ b/Modules/Controllers/Provider/ControllerBuilder.cs
@@ -17,10 +17,10 @@ public sealed class ControllerBuilder : IHandlerBuilder
private IBuilder? _Injection;
- private IBuilder? _Serializers;
-
private object? _Instance;
+ private IBuilder? _Serializers;
+
#region Functionality
public ControllerBuilder Serializers(IBuilder registry)
@@ -69,7 +69,9 @@ public IHandler Build(IHandler parent)
var instance = _Instance ?? throw new BuilderMissingPropertyException("Instance or Type");
- return Concerns.Chain(parent, _Concerns, p => new ControllerHandler(p, instance, serializers, injectors, formatters));
+ var extensions = new MethodRegistry(serializers, injectors, formatters);
+
+ return Concerns.Chain(parent, _Concerns, p => new ControllerHandler(p, instance, extensions));
}
#endregion
diff --git a/Modules/Controllers/Provider/ControllerHandler.cs b/Modules/Controllers/Provider/ControllerHandler.cs
index ce0921e9..dd6a9c0c 100644
--- a/Modules/Controllers/Provider/ControllerHandler.cs
+++ b/Modules/Controllers/Provider/ControllerHandler.cs
@@ -2,28 +2,24 @@
using System.Text.RegularExpressions;
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
-using GenHTTP.Modules.Conversion.Formatters;
-using GenHTTP.Modules.Conversion.Serializers;
using GenHTTP.Modules.Reflection;
-using GenHTTP.Modules.Reflection.Injectors;
+using GenHTTP.Modules.Reflection.Operations;
namespace GenHTTP.Modules.Controllers.Provider;
-public sealed partial class ControllerHandler : IHandler
+public sealed partial class ControllerHandler : IHandler, IServiceMethodProvider
{
- private static readonly MethodRouting Empty = new("^(/|)$", true, false);
-
private static readonly Regex HyphenMatcher = CreateHyphenMatcher();
#region Get-/Setters
public IHandler Parent { get; }
- private MethodCollection Provider { get; }
+ public MethodCollection Methods { get; }
private ResponseProvider ResponseProvider { get; }
- private FormatterRegistry Formatting { get; }
+ private MethodRegistry Registry { get; }
private object Instance { get; }
@@ -31,19 +27,19 @@ public sealed partial class ControllerHandler : IHandler
#region Initialization
- public ControllerHandler(IHandler parent, object instance, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting)
+ public ControllerHandler(IHandler parent, object instance, MethodRegistry registry)
{
Parent = parent;
- Formatting = formatting;
+ Registry = registry;
Instance = instance;
- ResponseProvider = new ResponseProvider(serialization, formatting);
+ ResponseProvider = new ResponseProvider(registry);
- Provider = new MethodCollection(this, AnalyzeMethods(instance.GetType(), serialization, injection, formatting));
+ Methods = new MethodCollection(this, AnalyzeMethods(instance.GetType(), registry));
}
- private IEnumerable> AnalyzeMethods(Type type, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting)
+ private IEnumerable> AnalyzeMethods(Type type, MethodRegistry registry)
{
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
@@ -51,56 +47,40 @@ private IEnumerable> AnalyzeMethods(Type type, Ser
var arguments = FindPathArguments(method);
- var path = DeterminePath(method, arguments);
+ var operation = CreateOperation(method, arguments);
- yield return parent => new MethodHandler(parent, method, path, () => Instance, annotation, ResponseProvider.GetResponseAsync, serialization, injection, formatting);
+ yield return parent => new MethodHandler(parent, operation, Instance, annotation, registry);
}
}
- private static MethodRouting DeterminePath(MethodInfo method, List arguments)
+ private Operation CreateOperation(MethodInfo method, List arguments)
{
- var pathArgs = string.Join('/', arguments.Select(a => a.ToParameter()));
-
- var isWildcard = PathArguments.CheckWildcardRoute(method.ReturnType);
+ var pathArguments = string.Join('/', arguments.Select(a => $":{a}"));
if (method.Name == "Index")
{
- return pathArgs.Length > 0 ? new MethodRouting($"^/{pathArgs}/", false, isWildcard) : Empty;
+ return OperationBuilder.Create(pathArguments.Length > 0 ? $"/{pathArguments}/" : null, method, Registry, true);
}
var name = HypenCase(method.Name);
- var path = $"^/{name}";
+ var path = $"/{name}";
- return pathArgs.Length > 0 ? new MethodRouting( $"{path}/{pathArgs}/", false, isWildcard) : new MethodRouting($"{path}/", false, isWildcard);
+ return OperationBuilder.Create(pathArguments.Length > 0 ? $"{path}/{pathArguments}/" : $"{path}/", method, Registry, true);
}
- private List FindPathArguments(MethodInfo method)
+ private static List FindPathArguments(MethodInfo method)
{
var found = new List();
- var parameters = method.GetParameters();
-
- foreach (var parameter in parameters)
+ foreach (var parameter in method.GetParameters())
{
- if (parameter.GetCustomAttribute(true) is not null)
+ if (parameter.Name != null)
{
- if (!parameter.CanFormat(Formatting))
- {
- throw new InvalidOperationException("Parameters marked as 'FromPath' must be formattable (e.g. string or int)");
- }
-
- if (parameter.CheckNullable())
+ if (parameter.GetCustomAttribute(true) is not null)
{
- throw new InvalidOperationException("Parameters marked as 'FromPath' are not allowed to be nullable");
+ found.Add(parameter.Name);
}
-
- if (parameter.Name is null)
- {
- throw new InvalidOperationException("Parameters marked as 'FromPath' must have a name");
- }
-
- found.Add(parameter.Name);
}
}
@@ -116,9 +96,9 @@ private List FindPathArguments(MethodInfo method)
#region Functionality
- public ValueTask PrepareAsync() => Provider.PrepareAsync();
+ public ValueTask PrepareAsync() => Methods.PrepareAsync();
- public ValueTask HandleAsync(IRequest request) => Provider.HandleAsync(request);
+ public ValueTask HandleAsync(IRequest request) => Methods.HandleAsync(request);
#endregion
diff --git a/Modules/Conversion/Formatters/BoolFormatter.cs b/Modules/Conversion/Formatters/BoolFormatter.cs
index 32fbf796..0a627a27 100644
--- a/Modules/Conversion/Formatters/BoolFormatter.cs
+++ b/Modules/Conversion/Formatters/BoolFormatter.cs
@@ -22,5 +22,4 @@ public sealed class BoolFormatter : IFormatter
public string Write(object value, Type type) => (bool)value ? "1" : "0";
private static bool Compare(string value, string expected) => string.Equals(value, expected, StringComparison.InvariantCultureIgnoreCase);
-
}
diff --git a/Modules/Conversion/Formatters/DateOnlyFormatter.cs b/Modules/Conversion/Formatters/DateOnlyFormatter.cs
index 0c293863..b0aebc16 100644
--- a/Modules/Conversion/Formatters/DateOnlyFormatter.cs
+++ b/Modules/Conversion/Formatters/DateOnlyFormatter.cs
@@ -6,9 +6,6 @@ public sealed partial class DateOnlyFormatter : IFormatter
{
private static readonly Regex DateOnlyPattern = CreateDateOnlyPattern();
- [GeneratedRegex(@"^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$", RegexOptions.Compiled)]
- private static partial Regex CreateDateOnlyPattern();
-
public bool CanHandle(Type type) => type == typeof(DateOnly);
public object Read(string value, Type type)
@@ -29,4 +26,6 @@ public object Read(string value, Type type)
public string Write(object value, Type type) => ((DateOnly)value).ToString("yyyy-MM-dd");
+ [GeneratedRegex(@"^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$", RegexOptions.Compiled)]
+ private static partial Regex CreateDateOnlyPattern();
}
diff --git a/Modules/Conversion/Formatters/EnumFormatter.cs b/Modules/Conversion/Formatters/EnumFormatter.cs
index f0c92f18..e88a0056 100644
--- a/Modules/Conversion/Formatters/EnumFormatter.cs
+++ b/Modules/Conversion/Formatters/EnumFormatter.cs
@@ -8,5 +8,4 @@ public sealed class EnumFormatter : IFormatter
public object Read(string value, Type type) => Enum.Parse(type, value);
public string? Write(object value, Type type) => value.ToString();
-
}
diff --git a/Modules/Conversion/Formatters/GuidFormatter.cs b/Modules/Conversion/Formatters/GuidFormatter.cs
index 24f98dca..2ca5e498 100644
--- a/Modules/Conversion/Formatters/GuidFormatter.cs
+++ b/Modules/Conversion/Formatters/GuidFormatter.cs
@@ -8,5 +8,4 @@ public sealed class GuidFormatter : IFormatter
public object Read(string value, Type type) => Guid.Parse(value);
public string? Write(object value, Type type) => value.ToString();
-
}
diff --git a/Modules/Conversion/Formatters/IFormatter.cs b/Modules/Conversion/Formatters/IFormatter.cs
index fd3340c5..60ae306a 100644
--- a/Modules/Conversion/Formatters/IFormatter.cs
+++ b/Modules/Conversion/Formatters/IFormatter.cs
@@ -36,5 +36,4 @@ public interface IFormatter
/// or to generate form encoded data.
///
string? Write(object value, Type type);
-
}
diff --git a/Modules/Conversion/Formatters/PrimitiveFormatter.cs b/Modules/Conversion/Formatters/PrimitiveFormatter.cs
index 93babfef..272d61ca 100644
--- a/Modules/Conversion/Formatters/PrimitiveFormatter.cs
+++ b/Modules/Conversion/Formatters/PrimitiveFormatter.cs
@@ -10,5 +10,4 @@ public sealed class PrimitiveFormatter : IFormatter
public object Read(string value, Type type) => Convert.ChangeType(value, type, CultureInfo.InvariantCulture);
public string? Write(object value, Type type) => Convert.ChangeType(value, typeof(string), CultureInfo.InvariantCulture) as string;
-
}
diff --git a/Modules/Conversion/Formatters/StringFormatter.cs b/Modules/Conversion/Formatters/StringFormatter.cs
index bd1ba8f3..b0660f87 100644
--- a/Modules/Conversion/Formatters/StringFormatter.cs
+++ b/Modules/Conversion/Formatters/StringFormatter.cs
@@ -8,5 +8,4 @@ public sealed class StringFormatter : IFormatter
public object Read(string value, Type type) => value;
public string Write(object value, Type type) => (string)value;
-
}
diff --git a/Modules/Conversion/Formatting.cs b/Modules/Conversion/Formatting.cs
index 7f55bb5c..c5e55358 100644
--- a/Modules/Conversion/Formatting.cs
+++ b/Modules/Conversion/Formatting.cs
@@ -27,5 +27,4 @@ public static class Formatting
///
/// An empty formatter registry
public static FormatterBuilder Empty() => new();
-
}
diff --git a/Modules/Conversion/Serialization.cs b/Modules/Conversion/Serialization.cs
index 1756ea4b..58a8ee66 100644
--- a/Modules/Conversion/Serialization.cs
+++ b/Modules/Conversion/Serialization.cs
@@ -26,5 +26,4 @@ public static class Serialization
/// Returns an empty registry to be customized.
///
public static SerializationBuilder Empty() => new();
-
}
diff --git a/Modules/Conversion/Serializers/Forms/FormContent.cs b/Modules/Conversion/Serializers/Forms/FormContent.cs
index 8002f8e0..3e01318d 100644
--- a/Modules/Conversion/Serializers/Forms/FormContent.cs
+++ b/Modules/Conversion/Serializers/Forms/FormContent.cs
@@ -55,8 +55,8 @@ public async ValueTask WriteAsync(Stream target, uint bufferSize)
}
var replaced = query.ToString()?
- .Replace("+", "%20")
- .Replace("%2b", "+");
+ .Replace("+", "%20")
+ .Replace("%2b", "+");
await writer.WriteAsync(replaced);
}
diff --git a/Modules/Conversion/Serializers/SerializationRegistry.cs b/Modules/Conversion/Serializers/SerializationRegistry.cs
index cf205a59..c7a85e6d 100644
--- a/Modules/Conversion/Serializers/SerializationRegistry.cs
+++ b/Modules/Conversion/Serializers/SerializationRegistry.cs
@@ -25,7 +25,7 @@ public SerializationRegistry(FlexibleContentType defaultType,
private FlexibleContentType Default { get; }
- private Dictionary Formats { get; }
+ public IReadOnlyDictionary Formats { get; }
#endregion
diff --git a/Modules/Functional/Provider/InlineBuilder.cs b/Modules/Functional/Provider/InlineBuilder.cs
index aac4501e..cdbe1a44 100644
--- a/Modules/Functional/Provider/InlineBuilder.cs
+++ b/Modules/Functional/Provider/InlineBuilder.cs
@@ -60,7 +60,7 @@ public InlineBuilder Formatters(IBuilder registry)
/// Adds a route for a request of any type to the root of the handler.
///
/// The logic to be executed
- public InlineBuilder Any(Delegate function) => On(function, AllMethods, null);
+ public InlineBuilder Any(Delegate function) => On(function, AllMethods);
///
/// Adds a route for a request of any type to the specified path.
@@ -74,7 +74,9 @@ public InlineBuilder Formatters(IBuilder registry)
///
/// The logic to be executed
public InlineBuilder Get(Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Get) }, null);
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Get)
+ });
///
/// Adds a route for a GET request to the specified path.
@@ -82,63 +84,81 @@ public InlineBuilder Formatters(IBuilder registry)
/// The path of the request to handle (e.g. "/my-method")
/// The logic to be executed
public InlineBuilder Get(string path, Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Get) }, path);
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Get)
+ }, path);
///
/// Adds a route for a HEAD request to the root of the handler.
///
/// The logic to be executed
public InlineBuilder Head(Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Head) });
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Head)
+ });
///
/// Adds a route for a HEAD request to the specified path.
///
/// The path of the request to handle (e.g. "/my-method")
public InlineBuilder Head(string path, Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Head) }, path);
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Head)
+ }, path);
///
/// Adds a route for a POST request to the root of the handler.
///
/// The logic to be executed
public InlineBuilder Post(Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Post) });
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Post)
+ });
///
/// Adds a route for a POST request to the specified path.
///
/// The path of the request to handle (e.g. "/my-method")
public InlineBuilder Post(string path, Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Post) }, path);
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Post)
+ }, path);
///
/// Adds a route for a PUT request to the root of the handler.
///
/// The logic to be executed
public InlineBuilder Put(Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Put) });
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Put)
+ });
///
/// Adds a route for a PUT request to the specified path.
///
/// The path of the request to handle (e.g. "/my-method")
public InlineBuilder Put(string path, Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Put) }, path);
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Put)
+ }, path);
///
/// Adds a route for a DELETE request to the root of the handler.
///
/// The logic to be executed
public InlineBuilder Delete(Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Delete) });
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Delete)
+ });
///
/// Adds a route for a DELETE request to the specified path.
///
/// The path of the request to handle (e.g. "/my-method")
public InlineBuilder Delete(string path, Delegate function) => On(function, new HashSet
- { FlexibleRequestMethod.Get(RequestMethod.Delete) }, path);
+ {
+ FlexibleRequestMethod.Get(RequestMethod.Delete)
+ }, path);
///
/// Executes the given function for the specified path and method.
@@ -181,7 +201,9 @@ public IHandler Build(IHandler parent)
var formatters = (_Formatters ?? Formatting.Default()).Build();
- return Concerns.Chain(parent, _Concerns, p => new InlineHandler(p, _Functions, serializers, injectors, formatters));
+ var extensions = new MethodRegistry(serializers, injectors, formatters);
+
+ return Concerns.Chain(parent, _Concerns, p => new InlineHandler(p, _Functions, extensions));
}
#endregion
diff --git a/Modules/Functional/Provider/InlineHandler.cs b/Modules/Functional/Provider/InlineHandler.cs
index 0cfbbf35..0e3574e9 100644
--- a/Modules/Functional/Provider/InlineHandler.cs
+++ b/Modules/Functional/Provider/InlineHandler.cs
@@ -1,20 +1,18 @@
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
-using GenHTTP.Modules.Conversion.Formatters;
-using GenHTTP.Modules.Conversion.Serializers;
using GenHTTP.Modules.Reflection;
-using GenHTTP.Modules.Reflection.Injectors;
+using GenHTTP.Modules.Reflection.Operations;
namespace GenHTTP.Modules.Functional.Provider;
-public class InlineHandler : IHandler
+public class InlineHandler : IHandler, IServiceMethodProvider
{
#region Get-/Setters
public IHandler Parent { get; }
- private MethodCollection Methods { get; }
+ public MethodCollection Methods { get; }
private ResponseProvider ResponseProvider { get; }
@@ -22,28 +20,26 @@ public class InlineHandler : IHandler
#region Initialization
- public InlineHandler(IHandler parent, List functions, SerializationRegistry serialization, InjectionRegistry injection, FormatterRegistry formatting)
+ public InlineHandler(IHandler parent, List functions, MethodRegistry registry)
{
Parent = parent;
- ResponseProvider = new ResponseProvider(serialization, formatting);
+ ResponseProvider = new ResponseProvider(registry);
- Methods = new MethodCollection(this, AnalyzeMethods(functions, serialization, injection, formatting));
+ Methods = new MethodCollection(this, AnalyzeMethods(functions, registry));
}
- private IEnumerable> AnalyzeMethods(List functions, SerializationRegistry formats, InjectionRegistry injection, FormatterRegistry formatting)
+ private static IEnumerable> AnalyzeMethods(List functions, MethodRegistry registry)
{
foreach (var function in functions)
{
var method = function.Delegate.Method;
- var wildcardRoute = PathArguments.CheckWildcardRoute(method.ReturnType);
-
- var path = PathArguments.Route(function.Path, wildcardRoute);
+ var operation = OperationBuilder.Create(function.Path, method, registry);
var target = function.Delegate.Target ?? throw new InvalidOperationException("Delegate target must not be null");
- yield return parent => new MethodHandler(parent, method, path, () => target, function.Configuration, ResponseProvider.GetResponseAsync, formats, injection, formatting);
+ yield return parent => new MethodHandler(parent, operation, target, function.Configuration, registry);
}
}
diff --git a/Modules/Layouting/Provider/LayoutRouter.cs b/Modules/Layouting/Provider/LayoutRouter.cs
index eb0c7441..7fe4ac84 100644
--- a/Modules/Layouting/Provider/LayoutRouter.cs
+++ b/Modules/Layouting/Provider/LayoutRouter.cs
@@ -29,11 +29,11 @@ public LayoutRouter(IHandler parent,
public IHandler Parent { get; }
- private Dictionary RoutedHandlers { get; }
+ public IReadOnlyDictionary RoutedHandlers { get; }
- private List RootHandlers { get; }
+ public IReadOnlyList RootHandlers { get; }
- private IHandler? Index { get; }
+ public IHandler? Index { get; }
#endregion
diff --git a/Modules/OpenApi/ApiDescription.cs b/Modules/OpenApi/ApiDescription.cs
new file mode 100644
index 00000000..3f0797c7
--- /dev/null
+++ b/Modules/OpenApi/ApiDescription.cs
@@ -0,0 +1,34 @@
+using GenHTTP.Modules.OpenApi.Discovery;
+using GenHTTP.Modules.OpenApi.Handler;
+
+namespace GenHTTP.Modules.OpenApi;
+
+///
+/// Provides a concern that will analyze the inner handler and
+/// automatically generate an OpenAPI specification and
+/// provide it to the clients when requested.
+///
+public static class ApiDescription
+{
+
+ ///
+ /// Creates a pre-configured concern to be added to a layout or any other handler.
+ ///
+ /// The pre-configured concern that will generate an OpenAPI specification on request
+ ///
+ /// The generated concern will crawl through the inner handler chain and analyze the following
+ /// types of content: Layouts, Concerns, Functional Handlers, Webservices, Controllers.
+ /// If you use other handlers or specific concerns to provide your API, you will need to implement
+ /// instances and pass them to the method.
+ ///
+ public static OpenApiConcernBuilder Create() => With(ApiDiscovery.Default());
+
+ ///
+ /// Creates a concern that will use the given discovery configuration to search for API endpoints
+ /// to be added to the generated OpenAPI specification.
+ ///
+ /// The explorer registry to be used to analyze the handler chain
+ /// The newly generated concern
+ public static OpenApiConcernBuilder With(ApiDiscoveryRegistryBuilder discovery) => new(discovery.Build());
+
+}
diff --git a/Modules/OpenApi/ApiDiscovery.cs b/Modules/OpenApi/ApiDiscovery.cs
new file mode 100644
index 00000000..d708367e
--- /dev/null
+++ b/Modules/OpenApi/ApiDiscovery.cs
@@ -0,0 +1,28 @@
+using GenHTTP.Modules.OpenApi.Discovery;
+
+namespace GenHTTP.Modules.OpenApi;
+
+///
+/// Provides capabilities used by the OpenAPI generation feature to analyze
+/// the functionality provided by your API.
+///
+public static class ApiDiscovery
+{
+
+ ///
+ /// Creates an empty registry.
+ ///
+ /// The newly created, empty registry
+ public static ApiDiscoveryRegistryBuilder Empty() => new();
+
+ ///
+ /// Creates a registry that supports layouts, concerns, functional handlers,
+ /// controllers and webservices for automatic content discovery.
+ ///
+ /// The default registry to use as a basis
+ public static ApiDiscoveryRegistryBuilder Default() => Empty().Add()
+ .Add()
+ .Add()
+ .Add()
+ .Add();
+}
diff --git a/Modules/OpenApi/Discovery/ApiDiscoveryRegistry.cs b/Modules/OpenApi/Discovery/ApiDiscoveryRegistry.cs
new file mode 100644
index 00000000..1ab1a71c
--- /dev/null
+++ b/Modules/OpenApi/Discovery/ApiDiscoveryRegistry.cs
@@ -0,0 +1,48 @@
+using GenHTTP.Api.Content;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+public sealed class ApiDiscoveryRegistry
+{
+
+ #region Get-/Setters
+
+ private List Explorers { get; }
+
+ #endregion
+
+ #region Initialization
+
+ public ApiDiscoveryRegistry(List explorers)
+ {
+ Explorers = explorers;
+ }
+
+ #endregion
+
+ #region Functionality
+
+ ///
+ /// Iterates through the registered explorers to find a responsible one to analyze
+ /// the given handler instance.
+ ///
+ /// The handler to get analyzed
+ /// The current stack of path segments that have already been analyzed, relative to the location of the OpenAPI concern
+ /// The document to be adjusted and enriched
+ /// The manager to generate JSON schemas with
+ public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata)
+ {
+ foreach (var explorer in Explorers)
+ {
+ if (explorer.CanExplore(handler))
+ {
+ explorer.Explore(handler, path, document, schemata, this);
+ break;
+ }
+ }
+ }
+
+ #endregion
+
+}
diff --git a/Modules/OpenApi/Discovery/ApiDiscoveryRegistryBuilder.cs b/Modules/OpenApi/Discovery/ApiDiscoveryRegistryBuilder.cs
new file mode 100644
index 00000000..ee632011
--- /dev/null
+++ b/Modules/OpenApi/Discovery/ApiDiscoveryRegistryBuilder.cs
@@ -0,0 +1,31 @@
+using GenHTTP.Api.Infrastructure;
+
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+public sealed class ApiDiscoveryRegistryBuilder : IBuilder
+{
+ private readonly List _Explorers = [];
+
+ #region Functionality
+
+ ///
+ /// Adds the given explorer to the registry.
+ ///
+ /// The type of the explorer to be added
+ public ApiDiscoveryRegistryBuilder Add() where TExplorer : IApiExplorer, new() => Add(new TExplorer());
+
+ ///
+ /// Adds the given explorer to the registry.
+ ///
+ /// The explorer to be added
+ public ApiDiscoveryRegistryBuilder Add(IApiExplorer explorer)
+ {
+ _Explorers.Add(explorer);
+ return this;
+ }
+
+ public ApiDiscoveryRegistry Build() => new(_Explorers);
+
+ #endregion
+
+}
diff --git a/Modules/OpenApi/Discovery/ConcernExplorer.cs b/Modules/OpenApi/Discovery/ConcernExplorer.cs
new file mode 100644
index 00000000..84e2bcf9
--- /dev/null
+++ b/Modules/OpenApi/Discovery/ConcernExplorer.cs
@@ -0,0 +1,19 @@
+using GenHTTP.Api.Content;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+public sealed class ConcernExplorer : IApiExplorer
+{
+
+ public bool CanExplore(IHandler handler) => handler is IConcern;
+
+ public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ {
+ if (handler is IConcern concern)
+ {
+ registry.Explore(concern.Content, path, document, schemata);
+ }
+ }
+
+}
diff --git a/Modules/OpenApi/Discovery/Extensions.cs b/Modules/OpenApi/Discovery/Extensions.cs
new file mode 100644
index 00000000..8012511c
--- /dev/null
+++ b/Modules/OpenApi/Discovery/Extensions.cs
@@ -0,0 +1,16 @@
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+internal static class Extensions
+{
+
+ internal static bool MightBeNull(this Type type)
+ {
+ if (type.IsClass)
+ {
+ return true;
+ }
+
+ return Nullable.GetUnderlyingType(type) != null;
+ }
+
+}
diff --git a/Modules/OpenApi/Discovery/LayoutExplorer.cs b/Modules/OpenApi/Discovery/LayoutExplorer.cs
new file mode 100644
index 00000000..6f14217a
--- /dev/null
+++ b/Modules/OpenApi/Discovery/LayoutExplorer.cs
@@ -0,0 +1,27 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Modules.Layouting.Provider;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+public sealed class LayoutExplorer : IApiExplorer
+{
+
+ public bool CanExplore(IHandler handler) => handler is LayoutRouter;
+
+ public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ {
+ if (handler is LayoutRouter layout)
+ {
+ foreach (var root in layout.RootHandlers)
+ {
+ registry.Explore(root, path, document, schemata);
+ }
+
+ foreach (var (route, routeHandler) in layout.RoutedHandlers)
+ {
+ registry.Explore(routeHandler, [..path, route], document, schemata);
+ }
+ }
+ }
+}
diff --git a/Modules/OpenApi/Discovery/MethodCollectionExplorer.cs b/Modules/OpenApi/Discovery/MethodCollectionExplorer.cs
new file mode 100644
index 00000000..9b53ebb3
--- /dev/null
+++ b/Modules/OpenApi/Discovery/MethodCollectionExplorer.cs
@@ -0,0 +1,22 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Modules.Reflection;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+public sealed class MethodCollectionExplorer : IApiExplorer
+{
+
+ public bool CanExplore(IHandler handler) => handler is MethodCollection;
+
+ public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ {
+ if (handler is MethodCollection collection)
+ {
+ foreach (var method in collection.Methods)
+ {
+ registry.Explore(method, path, document, schemata);
+ }
+ }
+ }
+}
diff --git a/Modules/OpenApi/Discovery/MethodHandlerExplorer.cs b/Modules/OpenApi/Discovery/MethodHandlerExplorer.cs
new file mode 100644
index 00000000..f232e331
--- /dev/null
+++ b/Modules/OpenApi/Discovery/MethodHandlerExplorer.cs
@@ -0,0 +1,276 @@
+using System.Text;
+using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+using GenHTTP.Modules.Reflection;
+using GenHTTP.Modules.Reflection.Operations;
+using NJsonSchema;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+public sealed class MethodHandlerExplorer : IApiExplorer
+{
+
+ public bool CanExplore(IHandler handler) => handler is MethodHandler;
+
+ public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ {
+ if (handler is MethodHandler methodHandler)
+ {
+ var tag = GetTag(methodHandler.Operation);
+
+ if (tag != null)
+ {
+ if (document.Tags.All(t => t.Name != tag))
+ {
+ document.Tags.Add(new OpenApiTag
+ {
+ Name = tag
+ });
+ }
+ }
+
+ var pathItem = GetPathItem(document, path, methodHandler.Operation);
+
+ foreach (var method in methodHandler.Configuration.SupportedMethods)
+ {
+ if (method == RequestMethod.Head && methodHandler.Configuration.SupportedMethods.Count > 1)
+ {
+ continue;
+ }
+
+ var operation = new OpenApiOperation
+ {
+ IsDeprecated = methodHandler.Operation.Method.GetCustomAttributes(typeof(ObsoleteAttribute), true).Length > 0
+ };
+
+ if (tag != null)
+ {
+ operation.Tags.Add(tag);
+ }
+
+ foreach (var arg in methodHandler.Operation.Arguments)
+ {
+ if (arg.Value.Source == OperationArgumentSource.Injected)
+ {
+ continue;
+ }
+
+ if (arg.Value.Source == OperationArgumentSource.Body)
+ {
+ if (method.KnownMethod != RequestMethod.Get)
+ {
+ operation.RequestBody = GetRequestBody(schemata, typeof(string), "text/plain");
+ }
+ }
+ else if (arg.Value.Source == OperationArgumentSource.Content)
+ {
+ if (method.KnownMethod != RequestMethod.Get)
+ {
+ var supportedTypes = methodHandler.Registry.Serialization.Formats.Select(s => s.Key.RawType).ToArray();
+ operation.RequestBody = GetRequestBody(schemata, arg.Value.Type, supportedTypes);
+ }
+ }
+ else if (arg.Value.Source == OperationArgumentSource.Streamed)
+ {
+ if (method.KnownMethod != RequestMethod.Get)
+ {
+ var body = new OpenApiRequestBody();
+
+ body.Content.Add("*/*", new OpenApiMediaType
+ {
+ Schema = new JsonSchema
+ {
+ Format = "binary"
+ }
+ });
+
+ operation.RequestBody = body;
+ }
+ }
+ else
+ {
+ var param = new OpenApiParameter
+ {
+ Name = arg.Key,
+ Schema = JsonSchema.FromType(arg.Value.Type),
+ Kind = MapArgumentType(arg.Value.Source),
+ IsRequired = MapRequired(arg.Value.Source)
+ };
+
+ operation.Parameters.Add(param);
+ }
+ }
+
+ foreach (var (key, value) in GetResponses(methodHandler.Operation, schemata, methodHandler.Registry))
+ {
+ operation.Responses.Add(key, value);
+ }
+
+ pathItem.Add(method.RawMethod, operation);
+ }
+ }
+ }
+
+ private static OpenApiPathItem GetPathItem(OpenApiDocument document, List path, Operation operation)
+ {
+ var stringPath = BuildPath(operation.Path.Name, path);
+
+ if (document.Paths.TryGetValue(stringPath, out var existing))
+ {
+ return existing;
+ }
+
+ var newPath = new OpenApiPathItem();
+
+ document.Paths.Add(stringPath, newPath);
+
+ return newPath;
+ }
+
+ private static string BuildPath(string name, List pathParts)
+ {
+ var builder = new StringBuilder("/");
+
+ if (pathParts.Count > 0)
+ {
+ builder.Append(string.Join('/', pathParts));
+ builder.Append('/');
+ }
+
+ if (name.Length > 0 && name[0] == '/')
+ {
+ builder.Append(name[1..]);
+ }
+ else
+ {
+ builder.Append(name);
+ }
+
+ return builder.ToString();
+ }
+
+ private static OpenApiParameterKind MapArgumentType(OperationArgumentSource source) => source switch
+ {
+ OperationArgumentSource.Path => OpenApiParameterKind.Path,
+ OperationArgumentSource.Body => OpenApiParameterKind.Body,
+ OperationArgumentSource.Content => OpenApiParameterKind.ModelBinding,
+ OperationArgumentSource.Query => OpenApiParameterKind.Query,
+ _ => OpenApiParameterKind.Undefined
+ };
+
+ private static bool MapRequired(OperationArgumentSource source) => source switch
+ {
+ OperationArgumentSource.Path => true,
+ OperationArgumentSource.Content => true,
+ _ => false
+ };
+
+ private static string? GetTag(Operation operation)
+ {
+ var type = operation.Method.DeclaringType?.Name;
+
+ if (type != null)
+ {
+ return type.Contains("<>") ? "Inline" : type;
+ }
+
+ return null;
+ }
+
+ private static Dictionary GetResponses(Operation operation, SchemaManager schemata, MethodRegistry registry)
+ {
+ var result = new Dictionary();
+
+ var sink = operation.Result.Sink;
+ var type = operation.Result.Type;
+
+ if (sink == OperationResultSink.None || type.MightBeNull())
+ {
+ result.Add("204", new OpenApiResponse
+ {
+ Description = "A response containing no body"
+ });
+ }
+
+ if (sink == OperationResultSink.Formatter)
+ {
+ result.Add("200", GetResponse(schemata, type, "text/plain"));
+ }
+ else if (sink == OperationResultSink.Serializer)
+ {
+ result.Add("200", GetResponse(schemata, type, registry.Serialization.Formats.Select(s => s.Key.RawType).ToArray()));
+ }
+ else if (sink == OperationResultSink.Stream)
+ {
+ var response = new OpenApiResponse
+ {
+ Description = "A dynamically generated response"
+ };
+
+ var schema = new JsonSchema
+ {
+ Format = "binary"
+ };
+
+ response.Content.Add("application/octet-stream", new OpenApiMediaType
+ {
+ Schema = schema
+ });
+
+ result.Add("200", response);
+ }
+ else if (sink == OperationResultSink.Dynamic)
+ {
+ var response = new OpenApiResponse
+ {
+ Description = "A dynamically generated response"
+ };
+
+ response.Content.Add("*/*", new OpenApiMediaType());
+
+ result.Add("200", response);
+ }
+
+ return result;
+ }
+
+ private static OpenApiRequestBody GetRequestBody(SchemaManager schemata, Type type, params string[] mediaTypes)
+ {
+ var requestBody = new OpenApiRequestBody();
+
+ var schema = schemata.GetOrCreateSchema(type);
+
+ foreach (var mediaType in mediaTypes)
+ {
+ var media = new OpenApiMediaType
+ {
+ Schema = schema
+ };
+
+ requestBody.Content.Add(mediaType, media);
+ }
+
+ return requestBody;
+ }
+
+ private static OpenApiResponse GetResponse(SchemaManager schemata, Type type, params string[] mediaTypes)
+ {
+ var response = new OpenApiResponse();
+
+ var schema = schemata.GetOrCreateSchema(type);
+
+ foreach (var mediaType in mediaTypes)
+ {
+ var media = new OpenApiMediaType
+ {
+ Schema = schema
+ };
+
+ response.Content.Add(mediaType, media);
+ }
+
+ return response;
+ }
+
+}
diff --git a/Modules/OpenApi/Discovery/SchemaManager.cs b/Modules/OpenApi/Discovery/SchemaManager.cs
new file mode 100644
index 00000000..491527c9
--- /dev/null
+++ b/Modules/OpenApi/Discovery/SchemaManager.cs
@@ -0,0 +1,34 @@
+using Namotion.Reflection;
+using NJsonSchema;
+using NJsonSchema.Generation;
+using NJsonSchema.NewtonsoftJson.Generation;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+public sealed class SchemaManager
+{
+ private readonly JsonSchemaGenerator _Generator;
+
+ private readonly OpenApiSchemaResolver _Resolver;
+
+ internal SchemaManager(OpenApiDocument document)
+ {
+ var settings = new NewtonsoftJsonSchemaGeneratorSettings
+ {
+ SchemaType = SchemaType.OpenApi3,
+ AllowReferencesWithProperties = true
+ };
+
+ _Generator = new JsonSchemaGenerator(settings);
+ _Resolver = new OpenApiSchemaResolver(document, settings);
+ }
+
+ ///
+ /// Generates or retrieves a JSON schema that represents the given type.
+ ///
+ /// The type to generate a schema for
+ /// The generated or retrieved JSON schema
+ public JsonSchema GetOrCreateSchema(Type type) => _Generator.GenerateWithReferenceAndNullability(type.ToContextualType(), false, _Resolver);
+
+}
diff --git a/Modules/OpenApi/Discovery/ServiceExplorer.cs b/Modules/OpenApi/Discovery/ServiceExplorer.cs
new file mode 100644
index 00000000..0205d082
--- /dev/null
+++ b/Modules/OpenApi/Discovery/ServiceExplorer.cs
@@ -0,0 +1,20 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Modules.Reflection;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Discovery;
+
+public sealed class ServiceExplorer : IApiExplorer
+{
+
+ public bool CanExplore(IHandler handler) => handler is IServiceMethodProvider;
+
+ public void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry)
+ {
+ if (handler is IServiceMethodProvider serviceProvider)
+ {
+ registry.Explore(serviceProvider.Methods, path, document, schemata);
+ }
+ }
+
+}
diff --git a/Modules/OpenApi/GenHTTP.Modules.OpenApi.csproj b/Modules/OpenApi/GenHTTP.Modules.OpenApi.csproj
new file mode 100644
index 00000000..6c34fd03
--- /dev/null
+++ b/Modules/OpenApi/GenHTTP.Modules.OpenApi.csproj
@@ -0,0 +1,61 @@
+
+
+
+
+ net8.0;net9.0
+
+ 13.0
+ enable
+ true
+ enable
+
+ 9.0.0.0
+ 9.0.0.0
+ 9.0.0
+
+ Andreas Nägeli
+
+
+ LICENSE
+ https://genhttp.org/
+
+ Adds an endpoint to your service that describes the API using the Open API format
+ HTTP Webserver C# Module OpenAPI
+
+ true
+ true
+ snupkg
+
+ true
+ CS1591,CS1587,CS1572,CS1573
+
+ icon.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Modules/OpenApi/Handler/OpenApiConcern.cs b/Modules/OpenApi/Handler/OpenApiConcern.cs
new file mode 100644
index 00000000..345c0788
--- /dev/null
+++ b/Modules/OpenApi/Handler/OpenApiConcern.cs
@@ -0,0 +1,137 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+using GenHTTP.Modules.OpenApi.Discovery;
+using NJsonSchema;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Handler;
+
+public sealed class OpenApiConcern : IConcern
+{
+ private OpenApiDocument? _Cached;
+
+ #region Initialization
+
+ public OpenApiConcern(IHandler parent, Func contentFactory, ApiDiscoveryRegistry discovery, bool enableCaching, Action postProcessor)
+ {
+ Parent = parent;
+ Content = contentFactory(this);
+
+ Discovery = discovery;
+ EnableCaching = enableCaching;
+ PostProcessor = postProcessor;
+ }
+
+ #endregion
+
+ #region Get-/Setters
+
+ public IHandler Parent { get; }
+
+ public IHandler Content { get; }
+
+ private ApiDiscoveryRegistry Discovery { get; }
+
+ private bool EnableCaching { get; }
+
+ private Action PostProcessor { get; }
+
+ #endregion
+
+ #region Functionality
+
+ public ValueTask PrepareAsync() => new();
+
+ public async ValueTask HandleAsync(IRequest request)
+ {
+ var path = request.Target.Current?.Original;
+
+ if (request.Method == RequestMethod.Get || request.Method == RequestMethod.Head)
+ {
+ if (string.Compare(path, "openapi", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ IResponse response;
+
+ if (request.Headers.TryGetValue("Accept", out var accept))
+ {
+ response = accept.ToLowerInvariant() switch
+ {
+ "application/json" or "application/application/vnd.oai.openapi+json" => GetDocument(request, OpenApiFormat.Json),
+ "application/yaml" or "application/application/vnd.oai.openapi+yaml" => GetDocument(request, OpenApiFormat.Yaml),
+ _ => throw new ProviderException(ResponseStatus.BadRequest, $"Generating API specifications of format '{accept}' is not supported")
+ };
+ }
+ else
+ {
+ response = GetDocument(request, OpenApiFormat.Json);
+ }
+
+ response.Headers.Add("Vary", "Accept");
+
+ return response;
+ }
+ if (string.Compare(path, "openapi.json", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ return GetDocument(request, OpenApiFormat.Json);
+ }
+ if (string.Compare(path, "openapi.yaml", StringComparison.OrdinalIgnoreCase) == 0 || string.Compare(path, "openapi.yml", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ return GetDocument(request, OpenApiFormat.Yaml);
+ }
+ }
+
+ return await Content.HandleAsync(request);
+ }
+
+ private IResponse GetDocument(IRequest request, OpenApiFormat format)
+ {
+ var document = Discover(request, Discovery);
+
+ var content = new OpenApiContent(document, format);
+
+ var contentType = format == OpenApiFormat.Json ? FlexibleContentType.Get(ContentType.ApplicationJson) : FlexibleContentType.Get(ContentType.ApplicationYaml);
+
+ return request.Respond()
+ .Content(content)
+ .Type(contentType)
+ .Build();
+ }
+
+ private OpenApiDocument Discover(IRequest request, ApiDiscoveryRegistry registry)
+ {
+ if (EnableCaching && _Cached != null)
+ {
+ return _Cached;
+ }
+
+ var document = new OpenApiDocument();
+
+ var schemata = new SchemaManager(document);
+
+ document.SchemaType = SchemaType.OpenApi3;
+
+ var path = request.Target.Path.ToString();
+
+ if (request.Host != null)
+ {
+ document.Servers.Add(new OpenApiServer
+ {
+ Url = (request.EndPoint.Secure ? "https://" : "http://") + request.Host + path[..path.LastIndexOf('/')]
+ });
+ }
+
+ registry.Explore(Content, [], document, schemata);
+
+ PostProcessor.Invoke(request, document);
+
+ if (EnableCaching)
+ {
+ _Cached = document;
+ }
+
+ return document;
+ }
+
+ #endregion
+
+}
diff --git a/Modules/OpenApi/Handler/OpenApiConcernBuilder.cs b/Modules/OpenApi/Handler/OpenApiConcernBuilder.cs
new file mode 100644
index 00000000..644f4ba2
--- /dev/null
+++ b/Modules/OpenApi/Handler/OpenApiConcernBuilder.cs
@@ -0,0 +1,94 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Api.Protocol;
+using GenHTTP.Modules.OpenApi.Discovery;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Handler;
+
+public sealed class OpenApiConcernBuilder : IConcernBuilder
+{
+ private bool _Caching = true;
+
+ private Action? _PostProcessor;
+
+ private string? _Title, _Version;
+
+ #region Initialization
+
+ public OpenApiConcernBuilder(ApiDiscoveryRegistry registry)
+ {
+ Discovery = registry;
+ }
+
+ #endregion
+
+ #region Get-/Setters
+
+ private ApiDiscoveryRegistry Discovery { get; }
+
+ #endregion
+
+ #region Functionality
+
+ ///
+ /// Sets the title of the OpenAPI specification.
+ ///
+ /// The title of the API
+ public OpenApiConcernBuilder Title(string title)
+ {
+ _Title = title;
+ return this;
+ }
+
+ ///
+ /// Sets the version of the described API.
+ ///
+ /// The version of the API
+ public OpenApiConcernBuilder Version(string version)
+ {
+ _Version = version;
+ return this;
+ }
+
+ ///
+ /// Specifies, whether the generated OpenAPI specification should
+ /// get cached on first request, so it is no re-generated on every request.
+ ///
+ /// Whether to use caching or not
+ public OpenApiConcernBuilder Caching(bool enabled)
+ {
+ _Caching = enabled;
+ return this;
+ }
+
+ ///
+ /// Registers a function that will be called when an OpenAPI document has been
+ /// generated, directly before it is served to the client.
+ ///
+ /// The method to be invoked to adjust the generated document
+ public OpenApiConcernBuilder PostProcessor(Action action)
+ {
+ _PostProcessor = action;
+ return this;
+ }
+
+ public IConcern Build(IHandler parent, Func contentFactory) => new OpenApiConcern(parent, contentFactory, Discovery, _Caching, DoPostProcessing);
+
+ private void DoPostProcessing(IRequest request, OpenApiDocument document)
+ {
+ if (_Title != null)
+ {
+ document.Info.Title = _Title;
+ }
+
+ if (_Version != null)
+ {
+ document.Info.Version = _Version;
+ }
+
+ _PostProcessor?.Invoke(request, document);
+ }
+
+ #endregion
+
+}
diff --git a/Modules/OpenApi/Handler/OpenApiContent.cs b/Modules/OpenApi/Handler/OpenApiContent.cs
new file mode 100644
index 00000000..daab147d
--- /dev/null
+++ b/Modules/OpenApi/Handler/OpenApiContent.cs
@@ -0,0 +1,48 @@
+using System.Text;
+using GenHTTP.Api.Protocol;
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi.Handler;
+
+internal class OpenApiContent : IResponseContent
+{
+
+ #region Initialization
+
+ internal OpenApiContent(OpenApiDocument document, OpenApiFormat format)
+ {
+ Document = document;
+ Format = format;
+ }
+
+ #endregion
+
+ #region Get-/Setters
+
+ public ulong? Length => null;
+
+ private OpenApiDocument Document { get; }
+
+ private OpenApiFormat Format { get; }
+
+ #endregion
+
+ #region Functionality
+
+ public ValueTask CalculateChecksumAsync() => new((ulong)Document.ToJson().GetHashCode());
+
+ public async ValueTask WriteAsync(Stream target, uint bufferSize)
+ {
+ if (Format == OpenApiFormat.Json)
+ {
+ await target.WriteAsync(Encoding.UTF8.GetBytes(Document.ToJson()));
+ }
+ else
+ {
+ await target.WriteAsync(Encoding.UTF8.GetBytes(Document.ToYaml()));
+ }
+ }
+
+ #endregion
+
+}
diff --git a/Modules/OpenApi/Handler/OpenApiFormat.cs b/Modules/OpenApi/Handler/OpenApiFormat.cs
new file mode 100644
index 00000000..f0ccf0f9
--- /dev/null
+++ b/Modules/OpenApi/Handler/OpenApiFormat.cs
@@ -0,0 +1,7 @@
+namespace GenHTTP.Modules.OpenApi.Handler;
+
+internal enum OpenApiFormat
+{
+ Json,
+ Yaml
+}
diff --git a/Modules/OpenApi/IApiExplorer.cs b/Modules/OpenApi/IApiExplorer.cs
new file mode 100644
index 00000000..f5560c98
--- /dev/null
+++ b/Modules/OpenApi/IApiExplorer.cs
@@ -0,0 +1,37 @@
+using GenHTTP.Api.Content;
+using GenHTTP.Modules.OpenApi.Discovery;
+
+using NSwag;
+
+namespace GenHTTP.Modules.OpenApi;
+
+///
+/// Allows to analyze a specific kind of handler and adds the API endpoints defined
+/// by the handler to the resulting OpenAPI document.
+///
+public interface IApiExplorer
+{
+
+ ///
+ /// Specfies, whether the given handler is supported by this explorer.
+ ///
+ /// The handler to be inspected
+ /// true, if this explorer is capable of inspecting this explorer
+ ///
+ /// Note that this method is only allowed to return true if the explorer can actually
+ /// analyze this handler. No other explorer will be invoked for the handler in this case.
+ ///
+ bool CanExplore(IHandler handler);
+
+ ///
+ /// Analyzes the given handler and adds the endpoints defined by this handler to the resulting
+ /// OpenAPI document.
+ ///
+ /// The handler to be analyzed
+ /// The current stack of path segments that have already been analyzed, relative to the location of the OpenAPI concern
+ /// The document to be adjusted and enriched
+ /// The manager to generate JSON schemas with
+ /// The registry containing all active explorers which can be used to further analyze any child handler of the given handler instance
+ void Explore(IHandler handler, List path, OpenApiDocument document, SchemaManager schemata, ApiDiscoveryRegistry registry);
+
+}
diff --git a/Modules/Reflection/Extensions.cs b/Modules/Reflection/Extensions.cs
index 25c0ef98..56cb2262 100644
--- a/Modules/Reflection/Extensions.cs
+++ b/Modules/Reflection/Extensions.cs
@@ -5,6 +5,7 @@ namespace GenHTTP.Modules.Reflection;
public static class Extensions
{
+ private static readonly Type? VoidTaskResult = Type.GetType("System.Threading.Tasks.VoidTaskResult");
///
/// Checks, whether the given parameter can be passed via the URL.
@@ -30,6 +31,8 @@ public static class Extensions
public static bool IsAsyncGeneric(this Type resultType) => resultType.IsAssignableToGenericType(typeof(ValueTask<>)) || resultType.IsAssignableToGenericType(typeof(Task<>));
+ public static bool IsGenericallyVoid(this Type type) => type.GenericTypeArguments.Length == 1 && type.GenericTypeArguments[0] == VoidTaskResult;
+
public static bool IsAssignableToGenericType(this Type givenType, Type genericType)
{
var interfaceTypes = givenType.GetInterfaces();
@@ -56,5 +59,4 @@ public static bool IsAssignableToGenericType(this Type givenType, Type genericTy
return IsAssignableToGenericType(baseType, genericType);
}
-
}
diff --git a/Modules/Reflection/IMethodConfiguration.cs b/Modules/Reflection/IMethodConfiguration.cs
index 7c1c2835..bfea1fb6 100644
--- a/Modules/Reflection/IMethodConfiguration.cs
+++ b/Modules/Reflection/IMethodConfiguration.cs
@@ -13,5 +13,4 @@ public interface IMethodConfiguration
/// The HTTP verbs which are supported by this method.
///
public HashSet SupportedMethods { get; }
-
}
diff --git a/Modules/Reflection/IResultWrapper.cs b/Modules/Reflection/IResultWrapper.cs
index 68815fe8..c5eca7dc 100644
--- a/Modules/Reflection/IResultWrapper.cs
+++ b/Modules/Reflection/IResultWrapper.cs
@@ -20,4 +20,5 @@ internal interface IResultWrapper
///
/// The response builder to manipulate
void Apply(IResponseBuilder builder);
+
}
diff --git a/Modules/Reflection/IServiceMethodProvider.cs b/Modules/Reflection/IServiceMethodProvider.cs
new file mode 100644
index 00000000..3798415d
--- /dev/null
+++ b/Modules/Reflection/IServiceMethodProvider.cs
@@ -0,0 +1,16 @@
+namespace GenHTTP.Modules.Reflection;
+
+///
+/// Implemented by handlers that use the handler
+/// for response generation. Allows logic interested in generically analyzing
+/// such handlers (e.g. the OpenAPI concern) to stay loosely coupled.
+///
+public interface IServiceMethodProvider
+{
+
+ ///
+ /// The method collection actually serving requests.
+ ///
+ MethodCollection Methods { get; }
+
+}
diff --git a/Modules/Reflection/Injection.cs b/Modules/Reflection/Injection.cs
index 8e4ed041..12ca98c7 100644
--- a/Modules/Reflection/Injection.cs
+++ b/Modules/Reflection/Injection.cs
@@ -8,6 +8,5 @@ public static class Injection
public static InjectionRegistryBuilder Empty() => new();
public static InjectionRegistryBuilder Default() => new InjectionRegistryBuilder().Add(new RequestInjector())
- .Add(new RequestBodyInjector())
.Add(new HandlerInjector());
}
diff --git a/Modules/Reflection/Injectors/HandlerInjector.cs b/Modules/Reflection/Injectors/HandlerInjector.cs
index f120a0d4..a37e1603 100644
--- a/Modules/Reflection/Injectors/HandlerInjector.cs
+++ b/Modules/Reflection/Injectors/HandlerInjector.cs
@@ -9,5 +9,4 @@ public class HandlerInjector : IParameterInjector
public bool Supports(Type type) => type == typeof(IHandler);
public object GetValue(IHandler handler, IRequest request, Type targetType) => handler;
-
}
diff --git a/Modules/Reflection/Injectors/RequestBodyInjector.cs b/Modules/Reflection/Injectors/RequestBodyInjector.cs
deleted file mode 100644
index 13cb4096..00000000
--- a/Modules/Reflection/Injectors/RequestBodyInjector.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using GenHTTP.Api.Content;
-using GenHTTP.Api.Protocol;
-
-namespace GenHTTP.Modules.Reflection.Injectors;
-
-public class RequestBodyInjector : IParameterInjector
-{
-
- public bool Supports(Type type) => type == typeof(Stream);
-
- public object? GetValue(IHandler handler, IRequest request, Type targetType)
- {
- if (request.Content is null)
- {
- throw new ProviderException(ResponseStatus.BadRequest, "Request body expected");
- }
-
- return request.Content;
- }
-}
diff --git a/Modules/Reflection/MethodAttribute.cs b/Modules/Reflection/MethodAttribute.cs
index cde97500..5452ac05 100644
--- a/Modules/Reflection/MethodAttribute.cs
+++ b/Modules/Reflection/MethodAttribute.cs
@@ -39,7 +39,7 @@ public MethodAttribute()
/// The HTTP verbs supported by this method
public MethodAttribute(params RequestMethod[] methods)
{
- SupportedMethods = new HashSet(methods.Select(m => FlexibleRequestMethod.Get(m)));
+ SupportedMethods = new HashSet(methods.Select(FlexibleRequestMethod.Get));
}
///
diff --git a/Modules/Reflection/MethodCollection.cs b/Modules/Reflection/MethodCollection.cs
index 552372a0..571b2bc9 100644
--- a/Modules/Reflection/MethodCollection.cs
+++ b/Modules/Reflection/MethodCollection.cs
@@ -39,7 +39,7 @@ public MethodCollection(IHandler parent, IEnumerable 1)
{
// if there is only one non-wildcard, use this one
- var nonWildcards = methods.Where(m => !m.Routing.IsWildcard).ToList();
+ var nonWildcards = methods.Where(m => !m.Operation.Path.IsWildcard).ToList();
if (nonWildcards.Count == 1)
{
@@ -72,7 +72,7 @@ private List FindProviders(string path, FlexibleRequestMethod req
foreach (var method in Methods)
{
- if (method.Routing.IsIndex && path == "/")
+ if (method.Operation.Path.IsIndex && path == "/")
{
if (method.Configuration.SupportedMethods.Contains(requestedMethod))
{
@@ -85,7 +85,7 @@ private List FindProviders(string path, FlexibleRequestMethod req
}
else
{
- if (method.Routing.ParsedPath.IsMatch(path))
+ if (method.Operation.Path.Matcher.IsMatch(path))
{
if (method.Configuration.SupportedMethods.Contains(requestedMethod))
{
diff --git a/Modules/Reflection/MethodHandler.cs b/Modules/Reflection/MethodHandler.cs
index 8e3ed2e2..d8030cba 100644
--- a/Modules/Reflection/MethodHandler.cs
+++ b/Modules/Reflection/MethodHandler.cs
@@ -4,11 +4,8 @@
using GenHTTP.Api.Content;
using GenHTTP.Api.Protocol;
using GenHTTP.Api.Routing;
-using GenHTTP.Modules.Conversion;
-using GenHTTP.Modules.Conversion.Formatters;
-using GenHTTP.Modules.Conversion.Serializers;
using GenHTTP.Modules.Conversion.Serializers.Forms;
-using GenHTTP.Modules.Reflection.Injectors;
+using GenHTTP.Modules.Reflection.Operations;
namespace GenHTTP.Modules.Reflection;
@@ -24,50 +21,44 @@ public sealed class MethodHandler : IHandler
{
private static readonly object?[] NoArguments = [];
- private static readonly Type? VoidTaskResult = Type.GetType("System.Threading.Tasks.VoidTaskResult");
+ #region Get-/Setters
- #region Initialization
+ public IHandler Parent { get; }
- public MethodHandler(IHandler parent, MethodInfo method, MethodRouting routing, Func