finish implementing path parameters
This commit is contained in:
1 parent
a24543063b
commit
fecd40cd57
5 files changed
+236
-27
No files matched your search
@@ -84,8 +84,11 @@ public sealed class HttpServer {
|
||||
RegisterConverter<decimal>();
|
||||
}
|
||||
|
||||
private readonly Dictionary<(string path, string rType), EndpointInvocationInfo> simpleEndpointMethodInfos = new();
|
||||
private readonly MultiKeyDictionary<string, string, EndpointInvocationInfo> simpleEndpointMethodInfos = new(); // requestmethod, path
|
||||
private readonly MultiKeyDictionary<string, string, EndpointInvocationInfo> pathEndpointMethodInfos = new(); // requestmethod, path
|
||||
private readonly Dictionary<string, PathTree<EndpointInvocationInfo>> pathEndpointMethodInfosTrees = new(); // reqmethod : pathtree
|
||||
private static readonly Type[] expectedEndpointParameterTypes = new[] { typeof(RequestContext) };
|
||||
internal static readonly int expectedEndpointParameterPrefixCount = expectedEndpointParameterTypes.Length;
|
||||
|
||||
public void RegisterEndpointsFromType<T>(Func<T>? instanceFactory = null) where T : class { // T cannot be static, as generic args must be nonstatic
|
||||
if (stringToTypeParameterConverters.Count == 0)
|
||||
@@ -119,6 +122,8 @@ public sealed class HttpServer {
|
||||
|
||||
// check the rest of the method parameters
|
||||
var qparams = new List<QueryParameterInfo>();
|
||||
var pparams = new List<PathParameterInfo>();
|
||||
int mParamIndex = expectedEndpointParameterTypes.Length;
|
||||
for (int i = expectedEndpointParameterTypes.Length; i < methodParams.Length; i++) {
|
||||
var par = methodParams[i];
|
||||
var attr = par.GetCustomAttribute<ParameterAttribute>(false);
|
||||
@@ -128,18 +133,25 @@ public sealed class HttpServer {
|
||||
throw new ArgumentException($"A method argument cannot be tagged with both {nameof(ParameterAttribute)} and {nameof(PathParameterAttribute)}");
|
||||
}
|
||||
|
||||
if (!stringToTypeParameterConverters.ContainsKey(par.ParameterType)) {
|
||||
throw new MissingParameterConverterException($"Parameter converter for type {par.ParameterType} for parameter at index {i} of method {GetFancyMethodName()} has not been registered (yet)!");
|
||||
}
|
||||
|
||||
if (pathAttr != null) { // parameter is a path param
|
||||
|
||||
} else { // parameter is a normal one
|
||||
qparams.Add(new(
|
||||
attr?.Name ?? par.Name ?? throw new ArgumentException($"C# variable name of parameter at index {i} of method {GetFancyMethodName()} is null!"),
|
||||
pparams.Add(new(
|
||||
pathAttr?.Name ?? throw new ArgumentException($"C# variable name of path parameter at index {i} of method {GetFancyMethodName()} is null!"),
|
||||
par.ParameterType,
|
||||
mParamIndex++
|
||||
)
|
||||
);
|
||||
} else { // parameter is a normal query param
|
||||
qparams.Add(new(
|
||||
attr?.Name ?? par.Name ?? throw new ArgumentException($"C# variable name of query parameter at index {i} of method {GetFancyMethodName()} is null!"),
|
||||
par.ParameterType,
|
||||
mParamIndex++,
|
||||
attr?.IsOptional ?? false)
|
||||
);
|
||||
|
||||
if (!stringToTypeParameterConverters.ContainsKey(par.ParameterType)) {
|
||||
throw new MissingParameterConverterException($"Parameter converter for type {par.ParameterType} has not been registered (yet)!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,17 +165,35 @@ public sealed class HttpServer {
|
||||
|
||||
foreach (var location in attrib.Locations) {
|
||||
var normLocation = NormalizeUrlPath(location);
|
||||
int idx = normLocation.IndexOf('{');
|
||||
if (idx >= 0) {
|
||||
// this path contains path parameters
|
||||
throw new NotImplementedException("Path parameters are not yet implemented!");
|
||||
}
|
||||
|
||||
var reqMethod = Enum.GetName(attrib.RequestMethod) ?? throw new ArgumentException("Request method was undefined");
|
||||
mainLogger.Information($"Registered endpoint: '{reqMethod} {normLocation}'");
|
||||
simpleEndpointMethodInfos.Add((normLocation, reqMethod), new EndpointInvocationInfo(mi, qparams, requiredChecks, classInstance));
|
||||
|
||||
var pparamsCopy = new List<PathParameterInfo>(pparams);
|
||||
var splittedLocation = location[1..].Split('/');
|
||||
for (int i = 0; i < pparamsCopy.Count; i++) {
|
||||
var pp = pparamsCopy[i];
|
||||
var idx = Array.IndexOf(splittedLocation, pp.Name);
|
||||
Assert(idx != -1, "Path parameter name was incorrect?");
|
||||
pp.SegmentStartPos = idx;
|
||||
pparamsCopy[i] = pp;
|
||||
}
|
||||
|
||||
var epInvocInfo = new EndpointInvocationInfo(mi, pparamsCopy, qparams, requiredChecks, classInstance);
|
||||
if (pparams.Any()) {
|
||||
mainLogger.Information($"Registered path endpoint: '{reqMethod} {normLocation}'");
|
||||
Assert(normLocation[0] == '/');
|
||||
pathEndpointMethodInfos.Add(reqMethod, normLocation[1..], epInvocInfo);
|
||||
} else {
|
||||
mainLogger.Information($"Registered simple endpoint: '{reqMethod} {normLocation}'");
|
||||
simpleEndpointMethodInfos.Add(reqMethod, normLocation, epInvocInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild path trees
|
||||
pathEndpointMethodInfosTrees.Clear();
|
||||
foreach (var (reqMethod, d2) in pathEndpointMethodInfos.backingDict)
|
||||
pathEndpointMethodInfosTrees.Add(reqMethod, new(d2));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -229,13 +259,23 @@ public sealed class HttpServer {
|
||||
}
|
||||
try {
|
||||
|
||||
if (simpleEndpointMethodInfos.TryGetValue((reqPath, requestMethod), out var endpointInvocationInfo)) {
|
||||
/* Finding the endpoint that should process the request:
|
||||
* 1. Try to see if there is a simple endpoint where request method and path match
|
||||
* 2. Otherwise, try to see if a path-parameter-endpoint matches (duplicates throw an error on startup)
|
||||
* 3. Otherwise, check if it is inside a static serve path
|
||||
* 4. Otherwise, show 404 page */
|
||||
|
||||
EndpointInvocationInfo? pathEndpointInvocationInfo = null;
|
||||
if (simpleEndpointMethodInfos.TryGetValue(requestMethod, reqPath, out var simpleEndpointInvocationInfo) ||
|
||||
pathEndpointMethodInfosTrees.TryGetValue(requestMethod, out var pt) && pt.TryGetPath(reqPath, out pathEndpointInvocationInfo)) { // try to find simple or pathparam-endpoint
|
||||
var endpointInvocationInfo = simpleEndpointInvocationInfo ?? pathEndpointInvocationInfo ?? throw new Exception("retrieved endpoint is somehow null");
|
||||
var mi = endpointInvocationInfo.methodInfo;
|
||||
var qparams = endpointInvocationInfo.queryParameters;
|
||||
var pparams = endpointInvocationInfo.pathParameters;
|
||||
var args = splitted.Length == 2 ? splitted[1] : null;
|
||||
|
||||
var parsedQParams = new Dictionary<string, string>();
|
||||
var convertedQParamValues = new object[qparams.Count + 1];
|
||||
var convertedMParamValues = new object[expectedEndpointParameterTypes.Length + pparams.Count + qparams.Count];
|
||||
|
||||
// run the checks to see if the client is allowed to make this request
|
||||
if (!endpointInvocationInfo.CheckAll(rc.ListenerContext.Request)) { // if any check failed return Forbidden
|
||||
@@ -263,32 +303,45 @@ public sealed class HttpServer {
|
||||
|
||||
if (parsedQParams.TryGetValue(qparam.Name, out var qparamValue)) {
|
||||
if (stringToTypeParameterConverters[qparam.Type].TryConvertFromString(qparamValue, out object objRes)) {
|
||||
convertedQParamValues[i] = objRes;
|
||||
convertedMParamValues[qparam.ArgPos] = objRes;
|
||||
} else {
|
||||
await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (qparam.IsOptional) {
|
||||
convertedQParamValues[i] = null!;
|
||||
convertedMParamValues[qparam.ArgPos] = null!;
|
||||
} else {
|
||||
await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest, $"Missing required query parameter {qparam.Name}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else { // check for missing query parameters
|
||||
var requiredParams = qparams.Where(x => !x.IsOptional).Select(x => $"'{x.Name}'").ToList();
|
||||
if (requiredParams.Any()) {
|
||||
await HandleDefaultErrorPageAsync(rc, HttpStatusCode.BadRequest, $"Missing required query parameter(s): {string.Join(",", requiredParams)}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
convertedQParamValues[0] = rc;
|
||||
if (pparams.Count != 0) {
|
||||
var splittedReqPath = reqPath[1..].Split('/');
|
||||
for (int i = 0; i < pparams.Count; i++) {
|
||||
var pparam = pparams[i];
|
||||
if (pparam.IsCatchAll)
|
||||
convertedMParamValues[pparam.ArgPos] = string.Join('/', splittedReqPath[pparam.SegmentStartPos..]);
|
||||
else
|
||||
convertedMParamValues[pparam.ArgPos] = splittedReqPath[pparam.SegmentStartPos];
|
||||
}
|
||||
}
|
||||
|
||||
convertedMParamValues[0] = rc;
|
||||
rc.ParsedParameters = parsedQParams.AsReadOnly();
|
||||
|
||||
await (Task) (mi.Invoke(endpointInvocationInfo.typeInstanceReference, convertedQParamValues) ?? throw new NullReferenceException("Website func returned null unexpectedly"));
|
||||
} else {
|
||||
// todo read and convert pathparams
|
||||
|
||||
await (Task) (mi.Invoke(endpointInvocationInfo.typeInstanceReference, convertedMParamValues) ?? throw new NullReferenceException("Website func returned null unexpectedly"));
|
||||
} else { // try to find suitable static serve path
|
||||
if (requestMethod == "GET")
|
||||
foreach (var (k, v) in staticServePaths) {
|
||||
if (reqPath.StartsWith(k)) { // do a static serve
|
||||
|
||||
Reference in new issue
Block a user