diff --git a/SimpleHttpServer/HttpServer.cs b/SimpleHttpServer/HttpServer.cs index b738e30..b2dbe04 100644 --- a/SimpleHttpServer/HttpServer.cs +++ b/SimpleHttpServer/HttpServer.cs @@ -107,27 +107,39 @@ public sealed class HttpServer { Assert(mi.IsPublic, $"Method tagged with HttpEndpointAttribute must be public! ({GetFancyMethodName()})"); var methodParams = mi.GetParameters(); + // check the mandatory prefix parameters Assert(methodParams.Length >= expectedEndpointParameterTypes.Length); for (int i = 0; i < expectedEndpointParameterTypes.Length; i++) { Assert(methodParams[i].ParameterType.IsAssignableFrom(expectedEndpointParameterTypes[i]), $"Parameter at index {i} of {GetFancyMethodName()} is of a type that cannot contain the expected type {expectedEndpointParameterTypes[i].FullName}."); } + // check return type Assert(mi.ReturnType == typeof(Task), $"Return type of {GetFancyMethodName()} is not {typeof(Task)}!"); - + // check the rest of the method parameters var qparams = new List(); for (int i = expectedEndpointParameterTypes.Length; i < methodParams.Length; i++) { var par = methodParams[i]; var attr = par.GetCustomAttribute(false); - qparams.Add(new( - attr?.Name ?? par.Name ?? throw new ArgumentException($"C# variable name of parameter at index {i} of method {GetFancyMethodName()} is null!"), - par.ParameterType, - attr?.IsOptional ?? false) - ); + var pathAttr = par.GetCustomAttribute(false); - if (!stringToTypeParameterConverters.ContainsKey(par.ParameterType)) { - throw new MissingParameterConverterException($"Parameter converter for type {par.ParameterType} has not been registered (yet)!"); + if (attr != null && pathAttr != null) { + throw new ArgumentException($"A method argument cannot be tagged with both {nameof(ParameterAttribute)} and {nameof(PathParameterAttribute)}"); + } + + 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!"), + par.ParameterType, + attr?.IsOptional ?? false) + ); + + if (!stringToTypeParameterConverters.ContainsKey(par.ParameterType)) { + throw new MissingParameterConverterException($"Parameter converter for type {par.ParameterType} has not been registered (yet)!"); + } } } diff --git a/SimpleHttpServer/Types/ParameterAttribute.cs b/SimpleHttpServer/Types/ParameterAttribute.cs index b3f4148..07bfb70 100644 --- a/SimpleHttpServer/Types/ParameterAttribute.cs +++ b/SimpleHttpServer/Types/ParameterAttribute.cs @@ -5,9 +5,6 @@ /// [AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)] public sealed class ParameterAttribute : Attribute { - // See the attribute guidelines at - // http://go.microsoft.com/fwlink/?LinkId=85236 - public string Name { get; } public bool IsOptional { get; } public ParameterAttribute(string name, bool isOptional = false) { diff --git a/SimpleHttpServer/Types/PathParameterAttribute.cs b/SimpleHttpServer/Types/PathParameterAttribute.cs new file mode 100644 index 0000000..2737b37 --- /dev/null +++ b/SimpleHttpServer/Types/PathParameterAttribute.cs @@ -0,0 +1,24 @@ +namespace SimpleHttpServer.Types; + +/// +/// Specifies the name of a http endpoint path parameter. Path parameter names must be in the format $1, $2, $3, ..., and the end of the path may be $* +/// +[AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)] +public sealed class PathParameterAttribute : Attribute { + public string Name { get; } + public PathParameterAttribute(string name) { + if (string.IsNullOrWhiteSpace(name)) { + throw new ArgumentException($"'{nameof(name)}' cannot be null or whitespace.", nameof(name)); + } + + if (!name.StartsWith('$')) { + throw new ArgumentException($"'{nameof(name)}' must start with $.", nameof(name)); + } + + if (name.Contains(' ')) { + throw new ArgumentException($"'{nameof(name)}' must not contain spaces.", nameof(name)); + } + + Name = name; + } +}