using RestEase.Implementation; using System; using System.Linq; using System.Net.Http; using System.Reflection; using System.Collections.Generic; namespace RestEase { /// /// Creates REST API clients from a suitable interface. Your single point of interaction with RestEase /// public class RestClient { private static readonly MethodInfo forInstanceGenericMethodInfo = typeof(RestClient).GetTypeInfo().GetMethods().First(x => x.Name == "For" && !x.IsStatic && x.GetParameters().Length == 0 && x.IsGenericMethod); private static readonly MethodInfo forStaticGenericMethodInfo = typeof(RestClient).GetTypeInfo().GetMethods().First(x => x.Name == "For" && x.IsStatic && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(IRequester) && x.IsGenericMethod); private static readonly ImplementationFactory factory = new ImplementationFactory(); /// /// Name of the assembly in which interface implementations are built. Use in [assembly: InternalsVisibleTo(RestEase.FactoryAssemblyName)] to allow clients to be generated for internal interface types /// public const string FactoryAssemblyName = "RestEaseFactory"; /// /// Name of the key in that a request's is stored /// public const string HttpRequestMessageRequestInfoPropertyKey = "RestEaseRequestInfo"; private readonly HttpClient httpClient; #pragma warning disable CS0618 // Type or member is obsolete /// /// Gets or sets the deserializer used to deserialize responses /// /// /// Defaults to . /// /// This has the type for backwards-compatibility reasons. You should assign /// an instance of /// public IResponseDeserializer? ResponseDeserializer { get; set; } /// /// Gets or sets the serializer used to serialize request bodies (when [Body(BodySerializationMethod.Serialized)] is used) /// /// /// Defaults to . /// /// This has the type for backwards-compatibility reasons. You should assign /// an instance of . /// public IRequestBodySerializer? RequestBodySerializer { get; set; } /// /// Gets or sets the serializer used to serialize path parameters (when [Path(PathSerializationMethod.Serialized)] is used) /// /// /// Has no default value, explicit serializer implementation must be provided. /// public RequestPathParamSerializer? RequestPathParamSerializer { get; set; } /// /// Gets or sets the serializer used to serialize query parameters (when [Query(QuerySerializationMethod.Serialized)] is used) /// /// /// Defaults to . /// /// This has the type for backwards-compatibility reasons. You should assign /// an instance of . /// public IRequestQueryParamSerializer? RequestQueryParamSerializer { get; set; } #pragma warning restore CS0618 // Type or member is obsolete /// /// Gets or sets the builder to use to construct encoded query strings from various parmaeters /// /// /// Defaults to null, in which case the default building logic is used /// public QueryStringBuilder? QueryStringBuilder { get; set; } /// /// Gets or sets the used to format items using /// /// /// Defaults to null, in which case the current culture is used. /// public IFormatProvider? FormatProvider { get; set; } /// /// Initialises a new instance of the class, with the given Base URL /// /// Base URL of the API public RestClient(string baseUrl) { if (baseUrl == null) throw new ArgumentNullException(nameof(baseUrl)); this.httpClient = this.Initialize(new HttpClientHandler(), new Uri(baseUrl)); } /// /// Initialises a new instance of the class, with the given Base URL /// /// Base URL of the API public RestClient(Uri baseUrl) { if (baseUrl == null) throw new ArgumentNullException(nameof(baseUrl)); this.httpClient = this.Initialize(new HttpClientHandler(), baseUrl); } /// /// Initialises a new instance of the class, with the given Base URL and request modifier /// /// Base URL of the API /// Delegate called on every request public RestClient(string baseUrl, RequestModifier requestModifier) { if (baseUrl == null) throw new ArgumentNullException(nameof(baseUrl)); if (requestModifier == null) throw new ArgumentNullException(nameof(requestModifier)); this.httpClient = this.Initialize(new ModifyingClientHttpHandler(requestModifier), new Uri(baseUrl)); } /// /// Initialises a new instance of the class, with the given Base URL and request modifier /// /// Base URL of the API /// Delegate called on every request public RestClient(Uri baseUrl, RequestModifier requestModifier) { if (baseUrl == null) throw new ArgumentNullException(nameof(baseUrl)); if (requestModifier == null) throw new ArgumentNullException(nameof(requestModifier)); this.httpClient = this.Initialize(new ModifyingClientHttpHandler(requestModifier), baseUrl); } private HttpClient Initialize(HttpMessageHandler messageHandler, Uri baseUrl) { return new HttpClient(messageHandler) { BaseAddress = baseUrl, }; } /// /// Initialises a new instance of the class, using the given HttpClient /// /// HttpClient to use public RestClient(HttpClient httpClient) { this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); } /// /// Create an implementation for the given API interface /// /// Type of interface to implement /// An implementation which can be used to make REST requests public object For(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); var method = forInstanceGenericMethodInfo.MakeGenericMethod(type); return method.Invoke(this, new object[0]); } /// /// Create an implementation for the given API interface /// /// Type of interface to implement /// An implementation which can be used to make REST requests public T For() { var requester = this.CreateRequester(); return factory.CreateImplementation(requester); } private Requester CreateRequester() { var requester = new Requester(this.httpClient); if (this.RequestBodySerializer is RequestBodySerializer requestBodySerializer) requester.RequestBodySerializer = requestBodySerializer; else if (this.RequestBodySerializer != null) requester.RequestBodySerializer = new RequestBodySerializerWrapper(this.RequestBodySerializer); else requester.RequestBodySerializer = new JsonRequestBodySerializer(); requester.RequestPathParamSerializer = this.RequestPathParamSerializer; if (this.RequestQueryParamSerializer is RequestQueryParamSerializer requestQueryParamSerializer) requester.RequestQueryParamSerializer = requestQueryParamSerializer; else if (this.RequestQueryParamSerializer != null) requester.RequestQueryParamSerializer = new RequestQueryParamSerializerWrapper(this.RequestQueryParamSerializer); else requester.RequestQueryParamSerializer = new JsonRequestQueryParamSerializer(); if (this.ResponseDeserializer is ResponseDeserializer responseDeserializer) requester.ResponseDeserializer = responseDeserializer; else if (this.ResponseDeserializer != null) requester.ResponseDeserializer = new ResponseDeserializerWrapper(this.ResponseDeserializer); else requester.ResponseDeserializer = new JsonResponseDeserializer(); requester.QueryStringBuilder = this.QueryStringBuilder; requester.FormatProvider = this.FormatProvider; return requester; } /// /// Create a client using the given IRequester. This gives you the greatest ability to customise functionality /// /// Interface representing the API /// IRequester to use /// An implementation of that interface which you can use to invoke the API public static object For(Type type, IRequester requester) { if (type == null) throw new ArgumentNullException(nameof(type)); var method = forStaticGenericMethodInfo.MakeGenericMethod(type); return method.Invoke(null, new object[] { requester }); } /// /// Create a client using the given IRequester. This gives you the greatest ability to customise functionality /// /// Interface representing the API /// IRequester to use /// An implementation of that interface which you can use to invoke the API public static T For(IRequester requester) { return factory.CreateImplementation(requester); } /// /// Shortcut to create a client using the given url /// /// Interface representing the API /// Base URL /// An implementation of that interface which you can use to invoke the API public static T For(string baseUrl) { return new RestClient(baseUrl).For(); } /// /// Shortcut to create a client using the given url /// /// Interface representing the API /// Base URL /// An implementation of that interface which you can use to invoke the API public static T For(Uri baseUrl) { return new RestClient(baseUrl).For(); } /// /// Shortcut to create a client using the given HttpClient /// /// Interface representing the API /// HttpClient to use to make requests /// An implementation of that interface which you can use to invoke the API public static T For(HttpClient httpClient) { return new RestClient(httpClient).For(); } /// /// Shortcut to create a client using the given URL and request interceptor /// /// Interface representing the API /// Base URL /// Delegate called on every request /// An implementation of that interface which you can use to invoke the API public static T For(string baseUrl, RequestModifier requestModifier) { return new RestClient(baseUrl, requestModifier).For(); } /// /// Shortcut to create a client using the given URL and request interceptor /// /// Interface representing the API /// Base URL /// Delegate called on every request /// An implementation of that interface which you can use to invoke the API public static T For(Uri baseUrl, RequestModifier requestModifier) { return new RestClient(baseUrl, requestModifier).For(); } /// /// Create a client using the given base URL, and custom serializer and/or deserializer /// /// Interface representing the API /// Base URL /// Deserializer to use when deserializing responses /// Serializer to use when serializing request bodies, when appropriate /// An implementation of that interface which you can use to invoke the API [Obsolete("Use 'new RestClient(baseUrl) { ResponseDeserializer = responseDeserializer, RequestBodySerializer = requestBodySerializer }.For()' instead")] public static T For(string baseUrl, ResponseDeserializer? responseDeserializer = null, RequestBodySerializer? requestBodySerializer = null) { return new RestClient(baseUrl) { ResponseDeserializer = responseDeserializer, RequestBodySerializer = requestBodySerializer }.For(); } /// /// Create a client using the given base URL, and custom serializer and/or deserializer /// /// Interface representing the API /// Base URL /// Delegate called on every request /// Deserializer to use when deserializing responses /// Serializer to use when serializing request bodiess, when appropriate /// An implementation of that interface which you can use to invoke the API [Obsolete("Use 'new RestClient(baseUrl, requestModifier) { ResponseDeserializer = responseDeserializer, RequestBodySerializer = requestBodySerializer }.For()' instead")] public static T For(string baseUrl, RequestModifier requestModifier, ResponseDeserializer? responseDeserializer = null, RequestBodySerializer? requestBodySerializer = null) { return new RestClient(baseUrl, requestModifier) { ResponseDeserializer = responseDeserializer, RequestBodySerializer = requestBodySerializer }.For(); } /// /// Create a client using the given HttpClient, and custom serializer and/or deserializer /// /// Interface representing the API /// HttpClient to use to make requests /// Deserializer to use when deserializing responses /// Serializer to use when serializing request bodies, when appropriate /// An implementation of that interface which you can use to invoke the API [Obsolete("Use 'new RestClient(httpClient) { ResponseDeserializer = responseDeserializer, RequestBodySerializer = requestBodySerializer }.For()' instead")] public static T For(HttpClient httpClient, ResponseDeserializer? responseDeserializer = null, RequestBodySerializer? requestBodySerializer = null) { return new RestClient(httpClient) { ResponseDeserializer = responseDeserializer, RequestBodySerializer = requestBodySerializer }.For(); } #pragma warning disable CS0618 // Type or member is obsolete private class RequestBodySerializerWrapper : RequestBodySerializer { private readonly IRequestBodySerializer serializer; public RequestBodySerializerWrapper(IRequestBodySerializer serializer) => this.serializer = serializer; public override HttpContent SerializeBody(T body, RequestBodySerializerInfo info) => this.serializer.SerializeBody(body); } private class RequestQueryParamSerializerWrapper : RequestQueryParamSerializer { private readonly IRequestQueryParamSerializer serializer; public RequestQueryParamSerializerWrapper(IRequestQueryParamSerializer serializer) => this.serializer = serializer; public override IEnumerable> SerializeQueryCollectionParam(string name, IEnumerable values, RequestQueryParamSerializerInfo info) => this.serializer.SerializeQueryCollectionParam(name, values, info); public override IEnumerable> SerializeQueryParam(string name, T value, RequestQueryParamSerializerInfo info) => this.serializer.SerializeQueryParam(name, value, info); } private class ResponseDeserializerWrapper : ResponseDeserializer { private readonly IResponseDeserializer deserializer; public ResponseDeserializerWrapper(IResponseDeserializer deserializer) => this.deserializer = deserializer; public override T Deserialize(string? content, HttpResponseMessage response, ResponseDeserializerInfo info) => this.deserializer.Deserialize(content, response); } #pragma warning restore CS0618 // Type or member is obsolete } }