diff --git a/DragonFruit.Common.Data.Tests/Advanced/Objects/DatabaseUpdateRequest.cs b/DragonFruit.Common.Data.Tests/Advanced/Objects/DatabaseUpdateRequest.cs index 3f10cc2..046d861 100644 --- a/DragonFruit.Common.Data.Tests/Advanced/Objects/DatabaseUpdateRequest.cs +++ b/DragonFruit.Common.Data.Tests/Advanced/Objects/DatabaseUpdateRequest.cs @@ -12,7 +12,7 @@ internal class DatabaseUpdateRequest : ApiRequest protected override Methods Method => Methods.Post; - protected override DataTypes DataType => DataTypes.SerializedProperty; + protected override BodyType BodyType => BodyType.SerializedProperty; [RequestBody] public Employee Employee { get; set; } = new Employee diff --git a/DragonFruit.Common.Data.Tests/Handlers/AuthPreservingHandler/Objects/AuthRequest.cs b/DragonFruit.Common.Data.Tests/Handlers/AuthPreservingHandler/Objects/AuthRequest.cs index f63a9c4..4977dd9 100644 --- a/DragonFruit.Common.Data.Tests/Handlers/AuthPreservingHandler/Objects/AuthRequest.cs +++ b/DragonFruit.Common.Data.Tests/Handlers/AuthPreservingHandler/Objects/AuthRequest.cs @@ -11,7 +11,7 @@ public class AuthRequest : ApiRequest { public override string Path => "https://osu.ppy.sh/oauth/token"; protected override Methods Method => Methods.Post; - protected override DataTypes DataType => DataTypes.Encoded; + protected override BodyType BodyType => BodyType.Encoded; [FormParameter("grant_type")] public string Grant => "client_credentials"; diff --git a/DragonFruit.Common.Data/ApiClient.cs b/DragonFruit.Common.Data/ApiClient.cs index f5f0c89..0b05802 100644 --- a/DragonFruit.Common.Data/ApiClient.cs +++ b/DragonFruit.Common.Data/ApiClient.cs @@ -52,14 +52,14 @@ public ApiClient(ISerializer serializer) public string UserAgent { get; set; } /// - /// Additional headers to be sent with the requests + /// The Authorization header value /// - public HashableDictionary CustomHeaders { get; set; } = new HashableDictionary(); + public string Authorization { get; set; } /// - /// The Authorization header + /// Additional headers to be sent with the requests /// - public string Authorization { get; set; } + public HashableDictionary CustomHeaders { get; set; } = new HashableDictionary(); /// /// Optional to be consumed by the @@ -75,7 +75,7 @@ public ApiClient(ISerializer serializer) /// /// Defaults to /// - protected ISerializer Serializer { get; set; } + public ISerializer Serializer { get; set; } /// /// used by these requests. This is used by the library and as such, should **not** be disposed in any way @@ -87,11 +87,6 @@ public ApiClient(ISerializer serializer) /// protected virtual int AdjustmentTimeout => 200; - /// - /// Last made for using with - /// - private ApiRequest CachedRequest { get; set; } - #endregion #region Clients, Hashes and Locks @@ -136,8 +131,7 @@ protected virtual HttpClient GetClient() //lock for modification if (!Monitor.TryEnter(_clientAdjustmentLock, AdjustmentTimeout)) { - throw new TimeoutException( - $"The {nameof(ApiClient)} is being overloaded with reconstruction requests. Consider creating a separate {nameof(ApiClient)} and delegating clients to specific types of requests"); + throw new TimeoutException($"The {nameof(ApiClient)} is being overloaded with reconstruction requests. Consider creating a separate {nameof(ApiClient)} and delegating clients to specific types of requests"); } //wait for all ongoing requests to end @@ -238,28 +232,24 @@ protected virtual void SetupRequest(HttpRequestMessage request) public virtual HttpResponseMessage Perform(ApiRequest requestData) { ValidateRequest(requestData); - - // perform and return postProcess result - return InternalPerform(requestData.GetRequest(Serializer), response => response, false); + return Perform(requestData.Build(this)); } /// - /// Perform a pre-fabricated + /// Perform an with a specified return type. /// - public virtual HttpResponseMessage Perform(HttpRequestMessage request) + public virtual T Perform(ApiRequest requestData) where T : class { - return InternalPerform(request, response => response, false); + ValidateRequest(requestData); + return Perform(requestData.Build(this)); } /// - /// Perform an with a specified return type. + /// Perform a pre-fabricated /// - public virtual T Perform(ApiRequest requestData) where T : class + public virtual HttpResponseMessage Perform(HttpRequestMessage request) { - ValidateRequest(requestData); - var request = requestData.GetRequest(Serializer); - - return InternalPerform(request, response => ValidateAndProcess(response, request), true); + return InternalPerform(request, response => response, false); } /// @@ -298,14 +288,19 @@ HttpResponseMessage CopyProcess(HttpResponseMessage response) return response; //we're not using this so return anything... } - _ = InternalPerform(requestData.GetRequest(Serializer), CopyProcess, true); + _ = InternalPerform(requestData.Build(this), CopyProcess, true); } /// /// Internal procedure for performing a web-request /// + /// + /// While the consumer has the option to prevent disposal of the produced, + /// the passed is always disposed at the end of the request. + /// /// The request to perform /// to process the + /// Whether to dispose of the produced after has been invoked. protected T InternalPerform(HttpRequestMessage request, Func processResult, bool disposeResponse) { //get client and request (disposables) @@ -360,15 +355,14 @@ protected virtual T ValidateAndProcess(HttpResponseMessage response, HttpRequ /// The client can't be used because there is no auth url. protected virtual void ValidateRequest(ApiRequest requestData) { - //todo is there any benefit to trying to parse the url? - if (string.IsNullOrWhiteSpace(requestData.Path)) - { - throw new NullRequestException(); - } - - if (requestData.RequireAuth && (!requestData.Headers.IsValueCreated && string.IsNullOrEmpty(Authorization))) + // note request path is validated on build + if (requestData.RequireAuth && string.IsNullOrEmpty(Authorization)) { - throw new ClientValidationException("Authorization data expected, but not found"); + // check if we have a custom headerset in the request + if (!requestData.Headers.IsValueCreated || !requestData.Headers.Value.ContainsKey("Authorization")) + { + throw new ClientValidationException("Authorization header was expected, but not found (in request or client)"); + } } } } diff --git a/DragonFruit.Common.Data/ApiRequest.cs b/DragonFruit.Common.Data/ApiRequest.cs index 5adbed0..710c632 100644 --- a/DragonFruit.Common.Data/ApiRequest.cs +++ b/DragonFruit.Common.Data/ApiRequest.cs @@ -29,9 +29,9 @@ public abstract class ApiRequest protected virtual Methods Method => Methods.Get; /// - /// The to use (if there is a body to be sent) + /// The to use (if there is a body to be sent) /// - protected virtual DataTypes DataType { get; } + protected virtual BodyType BodyType { get; } /// /// Whether an auth header is required. @@ -39,7 +39,7 @@ public abstract class ApiRequest /// This was set to true but no auth header was specified. /// Automatically suppressed if the property has been initialised. /// - public virtual bool RequireAuth => false; + protected internal virtual bool RequireAuth => false; /// /// Custom Headers to send with this request. Overrides any custom header set in the with the same name. @@ -58,10 +58,10 @@ public abstract class ApiRequest /// Overridable property for configuring a custom body for this request /// /// - /// Only used when the is equal to + /// Only used when the is equal to /// /// - public virtual HttpContent BodyContent { get; } + protected virtual HttpContent BodyContent { get; } /// /// used for ToString() conversions when collecting attributed members @@ -106,19 +106,26 @@ internal IEnumerable> GetParameter() where T : I } } - internal object GetSingleParameterObject() where T : Attribute - { - var property = GetType().GetProperties() - .Single(x => Attribute.GetCustomAttribute(x, typeof(T)) is T); + internal object GetSingleParameterObject() where T : Attribute => + GetType().GetProperties() + .Single(x => Attribute.GetCustomAttribute(x, typeof(T)) is T) + .GetValue(this, null); - return property.GetValue(this, null); - } + public HttpRequestMessage Build(ApiClient client) => Build(client.Serializer); /// - /// Creates the default , which can then be overriden by + /// Creates a for this , which can then be modified manually or overriden by /// - internal HttpRequestMessage GetRequest(ISerializer serializer) + /// + /// This validates the and properties, throwing a if it's unsatisfied with the constraints + /// + public HttpRequestMessage Build(ISerializer serializer) { + if (!Path.StartsWith("http")) + { + throw new HttpRequestException("The request path is invalid (it must start with http or https)"); + } + var request = new HttpRequestMessage { RequestUri = new Uri(FullUrl) }; //generic setup @@ -139,7 +146,7 @@ internal HttpRequestMessage GetRequest(ISerializer serializer) break; case Methods.Patch: - request.Method = new HttpMethod("PATCH"); //in .NET standard 2 patch isn't implemented... + request.Method = new HttpMethod("PATCH"); //in .NET Standard 2.0 patch isn't implemented... request.Content = GetContent(serializer); break; @@ -152,6 +159,10 @@ internal HttpRequestMessage GetRequest(ISerializer serializer) request.Method = HttpMethod.Head; break; + case Methods.Trace: + request.Method = HttpMethod.Trace; + break; + default: throw new NotImplementedException(); } @@ -171,19 +182,19 @@ internal HttpRequestMessage GetRequest(ISerializer serializer) private HttpContent GetContent(ISerializer serializer) { - switch (DataType) + switch (BodyType) { - case DataTypes.Encoded: + case BodyType.Encoded: return new FormUrlEncodedContent(GetParameter()); - case DataTypes.Serialized: + case BodyType.Serialized: return serializer.Serialize(this); - case DataTypes.SerializedProperty: + case BodyType.SerializedProperty: var body = serializer.Serialize(GetSingleParameterObject()); return body; - case DataTypes.Custom: + case BodyType.Custom: return BodyContent; default: @@ -191,7 +202,5 @@ private HttpContent GetContent(ISerializer serializer) throw new ArgumentOutOfRangeException(); } } - - internal ApiRequest Clone() => (ApiRequest)MemberwiseClone(); } } diff --git a/DragonFruit.Common.Data/Methods.cs b/DragonFruit.Common.Data/Methods.cs index f0ef61f..0eda7f6 100644 --- a/DragonFruit.Common.Data/Methods.cs +++ b/DragonFruit.Common.Data/Methods.cs @@ -12,10 +12,11 @@ public enum Methods Post, Put, Patch, - Delete + Delete, + Trace } - public enum DataTypes + public enum BodyType { /// /// Finds all properties marked with and creates a url-form encoded content from them