some more work on api

This commit is contained in:
Pilzinsel64
2024-08-16 09:19:03 +02:00
parent 6f5e012cb6
commit df4adb8435
8 changed files with 90 additions and 27 deletions

View File

@@ -0,0 +1,10 @@
namespace Pilz.Extensions.Collections;
public static class IEnumerableExtensions
{
public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
{
foreach (var t in @this)
action(t);
}
}

View File

@@ -1,9 +1,11 @@
namespace Pilz.Net.Api; namespace Pilz.Net.Api;
public record class ApiAuthCheckEventArgs(string AuthKey) public class ApiAuthCheckEventArgs(string authKey) : EventArgs
{ {
private bool hasDenyed; private bool hasDenyed;
public string AuthKey { get; } = authKey;
public bool Valid { get; set; } public bool Valid { get; set; }
public void Deny() public void Deny()

View File

@@ -14,24 +14,24 @@ public class ApiClient(string apiUrl) : IApiClient
public ILogger Log { get; set; } = NullLogger.Instance; public ILogger Log { get; set; } = NullLogger.Instance;
public virtual async Task<ApiResponse> SendMessage<TResponse>(string url, ApiMessage message, IMessageSerializer? serializer) public virtual async Task<ApiResponse> SendMessage<TResponse>(string route, ApiMessage message, IMessageSerializer? serializer = null)
{ {
serializer ??= Serializer; serializer ??= Serializer;
Log.InfoFormat("Send message to {0}", url); Log.InfoFormat("Send message to {0}", route);
var res = await Send(url, message, serializer); var res = await Send(route, message, serializer);
return new(res.StatusCode); return new(res.StatusCode);
} }
public virtual async Task<ApiResponse<TResponse>> SendRequest<TResponse>(string url, ApiMessage message, IMessageSerializer? serializer) where TResponse : ApiMessage public virtual async Task<ApiResponse<TResponse>> SendRequest<TResponse>(string route, ApiMessage message, IMessageSerializer? serializer = null) where TResponse : ApiMessage
{ {
serializer ??= Serializer; serializer ??= Serializer;
Log.InfoFormat("Send request to {0}", url); Log.InfoFormat("Send request to {0}", route);
var res = await Send(url, message, serializer); var res = await Send(route, message, serializer);
TResponse? result = null; TResponse? result = null;
if (res.IsSuccessStatusCode) if (res.IsSuccessStatusCode)
@@ -40,11 +40,16 @@ public class ApiClient(string apiUrl) : IApiClient
return new(res.StatusCode, result); return new(res.StatusCode, result);
} }
protected virtual async Task<HttpResponseMessage> Send(string url, ApiMessage message, IMessageSerializer serializer) protected virtual async Task<HttpResponseMessage> Send(string route, ApiMessage message, IMessageSerializer serializer)
{ {
var fullRequestUrl = ApiUrl + url; var url = ApiUrl + route;
var content = new StringContent(serializer.Serialize(message)!, null, "application/json"); var content = new StringContent(serializer.Serialize(message)!, null, "application/json");
content.Headers.Add("API-AUTH-KEY", AuthKey); content.Headers.Add("API-AUTH-KEY", EncodeAuthKey());
return await httpClient.PostAsync(fullRequestUrl, content); return await httpClient.PostAsync(url, content);
}
protected virtual string? EncodeAuthKey()
{
return AuthKey;
} }
} }

View File

@@ -0,0 +1,9 @@
using System.Diagnostics.CodeAnalysis;
namespace Pilz.Net.Api;
public record class ApiRequestInfo(
ApiMessage Message,
[property: MemberNotNullWhen(true, "AuthKey")]
bool IsAuthenticated,
string? AuthKey);

View File

@@ -1,4 +1,5 @@
using System.Net; using System.Diagnostics.CodeAnalysis;
using System.Net;
namespace Pilz.Net.Api; namespace Pilz.Net.Api;
@@ -7,9 +8,10 @@ public record class ApiResponse<T>(
T? Message) T? Message)
where T : ApiMessage where T : ApiMessage
{ {
[MemberNotNull(nameof(Message))]
public void EnsureOk() public void EnsureOk()
{ {
if (StatusCode != HttpStatusCode.OK) if (StatusCode != HttpStatusCode.OK || Message is null)
throw new Exception("Api return is not ok!"); throw new Exception("Api return is not ok!");
} }
} }

View File

@@ -6,13 +6,21 @@ public record class ApiResult(
HttpStatusCode StatusCode, HttpStatusCode StatusCode,
ApiMessage? Message = null) ApiMessage? Message = null)
{ {
public static ApiResult Ok() public static ApiResult Ok() => new(HttpStatusCode.OK);
{
return new(HttpStatusCode.OK);
}
public static ApiResult Ok(ApiMessage message) public static ApiResult Ok(ApiMessage message) => new(HttpStatusCode.OK, message);
{
return new(HttpStatusCode.OK, message); public static ApiResult Unauthorized() => new(HttpStatusCode.Unauthorized);
}
public static ApiResult NotFound() => new(HttpStatusCode.NotFound);
public static ApiResult Forbidden() => new(HttpStatusCode.Forbidden);
public static ApiResult Locked() => new(HttpStatusCode.Locked);
public static ApiResult TooManyRequests() => new(HttpStatusCode.TooManyRequests);
public static ApiResult ServiceUnavailable() => new(HttpStatusCode.ServiceUnavailable);
public static ApiResult UnavailableForLegalReasons() => new(HttpStatusCode.UnavailableForLegalReasons);
} }

View File

@@ -54,7 +54,9 @@ public class ApiServer(string apiUrl) : IApiServer
// Sanity checks // Sanity checks
if (method.GetCustomAttribute<MessageHandlerAttribute>() is not MessageHandlerAttribute attribute if (method.GetCustomAttribute<MessageHandlerAttribute>() is not MessageHandlerAttribute attribute
|| method.GetParameters().FirstOrDefault() is not ParameterInfo parameter || method.GetParameters().FirstOrDefault() is not ParameterInfo parameter
|| !parameter.ParameterType.IsAssignableTo(typeof(ApiMessage)) || !parameter.ParameterType.IsGenericType
|| parameter.ParameterType.GenericTypeArguments.Length != 1
|| !parameter.ParameterType.GenericTypeArguments[0].IsAssignableTo(typeof(ApiMessage))
|| !method.ReturnType.IsAssignableTo(typeof(ApiResult))) || !method.ReturnType.IsAssignableTo(typeof(ApiResult)))
throw new NotSupportedException("The first parameter needs to be of type ApiMessage and must return an ApiResult object and the method must have the MessageHandlerAttribute."); throw new NotSupportedException("The first parameter needs to be of type ApiMessage and must return an ApiResult object and the method must have the MessageHandlerAttribute.");
@@ -146,8 +148,11 @@ public class ApiServer(string apiUrl) : IApiServer
// Check authentication // Check authentication
Log.Debug("Check authentication"); Log.Debug("Check authentication");
if (attribute.RequiesAuth && (string.IsNullOrWhiteSpace(authKey) || !CheckAuthentication(authKey))) var isAuthenticated = false;
return null; if (!string.IsNullOrWhiteSpace(authKey) && DecodeAuthKey(authKey) is string authKeyDecoded)
isAuthenticated = CheckAuthentication(authKeyDecoded);
else
authKeyDecoded = null!;
// Get required infos // Get required infos
Log.Debug("Find other infos"); Log.Debug("Find other infos");
@@ -161,7 +166,8 @@ public class ApiServer(string apiUrl) : IApiServer
// Invoke handler // Invoke handler
Log.Debug("Invoke handler"); Log.Debug("Invoke handler");
if (handler.DynamicInvoke(message) is not ApiResult result) var parameters = BuildParameters(handler, () => message, () => new(message, isAuthenticated, authKeyDecoded));
if (handler.DynamicInvoke(parameters) is not ApiResult result)
return null; return null;
// Return result without message // Return result without message
@@ -179,6 +185,22 @@ public class ApiServer(string apiUrl) : IApiServer
return new(result, resultStr); return new(result, resultStr);
} }
protected virtual object?[]? BuildParameters(Delegate handler, Func<ApiMessage> getMessage, Func<ApiRequestInfo> getRequestInfo)
{
var infos = handler.Method.GetParameters();
var objs = new List<object?>();
foreach (var info in infos)
{
if (info.ParameterType.IsAssignableTo(typeof(ApiMessage)))
objs.Add(getMessage());
else if (info.ParameterType.IsAssignableTo(typeof(ApiRequestInfo)))
objs.Add(getRequestInfo());
}
return [.. objs];
}
protected virtual IMessageSerializer GetSerializer(Type? t) protected virtual IMessageSerializer GetSerializer(Type? t)
{ {
if (t is not null) if (t is not null)
@@ -204,4 +226,9 @@ public class ApiServer(string apiUrl) : IApiServer
} }
return false; return false;
} }
protected virtual string? DecodeAuthKey(string authKey)
{
return authKey;
}
} }

View File

@@ -12,7 +12,7 @@ public interface IApiClient
ILogger Log { get; set; } ILogger Log { get; set; }
Task<ApiResponse> SendMessage<TResponse>(string url, ApiMessage message, IMessageSerializer? serializer = null); Task<ApiResponse> SendMessage<TResponse>(string route, ApiMessage message, IMessageSerializer? serializer = null);
Task<ApiResponse<TResponse>> SendRequest<TResponse>(string url, ApiMessage message, IMessageSerializer? serializer = null) where TResponse : ApiMessage; Task<ApiResponse<TResponse>> SendRequest<TResponse>(string route, ApiMessage message, IMessageSerializer? serializer = null) where TResponse : ApiMessage;
} }