-
Notifications
You must be signed in to change notification settings - Fork 2
Make RestClientContext a record and move logic to RestClientBuilder
#129
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,27 @@ | ||
| #nullable disable | ||
| using System; | ||
| using System.Net.Http; | ||
| using Activout.RestClient.DomainExceptions; | ||
| using Activout.RestClient.Helpers; | ||
| using Activout.RestClient.ParamConverter; | ||
| using Activout.RestClient.Serialization; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Activout.RestClient | ||
| namespace Activout.RestClient; | ||
|
|
||
| public interface IRestClientBuilder | ||
| { | ||
| public interface IRestClientBuilder | ||
| { | ||
| IRestClientBuilder BaseUri(Uri apiUri); | ||
| IRestClientBuilder ContentType(MediaType contentType); | ||
| IRestClientBuilder Header(string name, object value); | ||
| IRestClientBuilder With(ILogger logger); | ||
| IRestClientBuilder With(HttpClient httpClient); | ||
| IRestClientBuilder With(IRequestLogger requestLogger); | ||
| IRestClientBuilder With(IDeserializer deserializer); | ||
| IRestClientBuilder With(ISerializer serializer); | ||
| IRestClientBuilder With(ISerializationManager serializationManager); | ||
| IRestClientBuilder With(ITaskConverterFactory taskConverterFactory); | ||
| IRestClientBuilder With(IDomainExceptionMapperFactory domainExceptionMapperFactory); | ||
| T Build<T>() where T : class; | ||
| } | ||
| IRestClientBuilder BaseUri(Uri apiUri); | ||
| IRestClientBuilder ContentType(MediaType contentType); | ||
| IRestClientBuilder Header(string name, object value, bool isReplace = true); | ||
| IRestClientBuilder With(IDuckTyping duckTyping); | ||
| IRestClientBuilder With(ILogger logger); | ||
| IRestClientBuilder With(HttpClient httpClient); | ||
| IRestClientBuilder With(IRequestLogger requestLogger); | ||
| IRestClientBuilder With(IDeserializer deserializer); | ||
| IRestClientBuilder With(ISerializer serializer); | ||
| IRestClientBuilder With(ISerializationManager serializationManager); | ||
| IRestClientBuilder With(ITaskConverterFactory taskConverterFactory); | ||
| IRestClientBuilder With(IDomainExceptionMapperFactory domainExceptionMapperFactory); | ||
| IRestClientBuilder With(IParamConverterManager paramConverterManager); | ||
| T Build<T>() where T : class; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| using System; | ||
| using System.Net.Http; | ||
|
|
||
| namespace Activout.RestClient.Implementation; | ||
|
|
||
| internal sealed class DummyRequestLogger : IRequestLogger, IDisposable | ||
| { | ||
| public static IRequestLogger Instance { get; } = new DummyRequestLogger(); | ||
|
|
||
| private DummyRequestLogger() | ||
| { | ||
| } | ||
|
|
||
| public IDisposable TimeOperation(HttpRequestMessage httpRequestMessage) => this; | ||
|
|
||
| public void Dispose() | ||
| { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,19 @@ | ||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Activout.RestClient.Implementation | ||
| namespace Activout.RestClient.Implementation; | ||
|
|
||
| public static class HeaderListExtensions | ||
| { | ||
| public static class HeaderListExtensions | ||
| public static void AddOrReplaceHeader<T>(this List<KeyValuePair<string, T>> headers, string name, | ||
| T value, bool replace) | ||
| { | ||
| public static void AddOrReplaceHeader(this List<KeyValuePair<string, object>> headers, string name, | ||
| string value, bool replace) | ||
| if (replace) | ||
| { | ||
| if (replace) | ||
| { | ||
| headers.RemoveAll(header => | ||
| header.Key.Equals(name, StringComparison.InvariantCultureIgnoreCase)); | ||
| } | ||
|
|
||
| headers.Add(new KeyValuePair<string, object>(name, value)); | ||
| headers.RemoveAll(header => | ||
| header.Key.Equals(name, StringComparison.OrdinalIgnoreCase)); | ||
| } | ||
|
|
||
| headers.Add(new KeyValuePair<string, T>(name, value)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,76 +1,47 @@ | ||||||||||
| #nullable disable | ||||||||||
| using System; | ||||||||||
| using System.Collections.Concurrent; | ||||||||||
| using System.Collections.Generic; | ||||||||||
| using System.Dynamic; | ||||||||||
| using System.Linq; | ||||||||||
| using System.Reflection; | ||||||||||
| using Activout.RestClient.DomainExceptions; | ||||||||||
|
|
||||||||||
| namespace Activout.RestClient.Implementation | ||||||||||
| namespace Activout.RestClient.Implementation; | ||||||||||
|
|
||||||||||
| internal class RestClient : DynamicObject | ||||||||||
| { | ||||||||||
| internal class RestClient<T> : DynamicObject where T : class | ||||||||||
| { | ||||||||||
| private readonly RestClientContext _context; | ||||||||||
| private readonly Type _type; | ||||||||||
| private readonly RestClientContext _context; | ||||||||||
| private readonly Type _type; | ||||||||||
|
|
||||||||||
| private readonly IDictionary<MethodInfo, RequestHandler> _requestHandlers = | ||||||||||
| new ConcurrentDictionary<MethodInfo, RequestHandler>(); | ||||||||||
| private readonly IDictionary<MethodInfo, RequestHandler> _requestHandlers = | ||||||||||
| new ConcurrentDictionary<MethodInfo, RequestHandler>(); | ||||||||||
|
|
||||||||||
| public RestClient(RestClientContext context) | ||||||||||
| { | ||||||||||
| _type = typeof(T); | ||||||||||
| _context = context; | ||||||||||
| HandleAttributes(); | ||||||||||
| _context.DefaultSerializer = _context.SerializationManager.GetSerializer(_context.DefaultContentType); | ||||||||||
| } | ||||||||||
| public RestClient(Type type, RestClientContext context) | ||||||||||
| { | ||||||||||
| _type = type; | ||||||||||
| _context = context; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private void HandleAttributes() | ||||||||||
| { | ||||||||||
| var attributes = _type.GetCustomAttributes(); | ||||||||||
| foreach (var attribute in attributes) | ||||||||||
| switch (attribute) | ||||||||||
| { | ||||||||||
| case ContentTypeAttribute contentTypeAttribute: | ||||||||||
| _context.DefaultContentType = MediaType.ValueOf(contentTypeAttribute.ContentType); | ||||||||||
| break; | ||||||||||
| case DomainExceptionAttribute domainExceptionAttribute: | ||||||||||
| _context.DomainExceptionType = domainExceptionAttribute.Type; | ||||||||||
| break; | ||||||||||
| case ErrorResponseAttribute errorResponseAttribute: | ||||||||||
| _context.ErrorResponseType = errorResponseAttribute.Type; | ||||||||||
| break; | ||||||||||
| case HeaderAttribute headerAttribute: | ||||||||||
| _context.DefaultHeaders.AddOrReplaceHeader(headerAttribute.Name, headerAttribute.Value, headerAttribute.Replace); | ||||||||||
| break; | ||||||||||
| case PathAttribute pathAttribute: | ||||||||||
| _context.BaseTemplate = pathAttribute.Template; | ||||||||||
| break; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| public override IEnumerable<string> GetDynamicMemberNames() | ||||||||||
| { | ||||||||||
| return _type.GetMembers().Select(x => x.Name); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| public override IEnumerable<string> GetDynamicMemberNames() | ||||||||||
| public override bool TryInvokeMember(InvokeMemberBinder binder, object?[]? args, out object? result) | ||||||||||
| { | ||||||||||
| var method = _type.GetMethod(binder.Name); | ||||||||||
| if (method == null) | ||||||||||
| { | ||||||||||
| return _type.GetMembers().Select(x => x.Name); | ||||||||||
| result = null; | ||||||||||
| return false; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) | ||||||||||
| if (!_requestHandlers.TryGetValue(method, out var requestHandler)) | ||||||||||
| { | ||||||||||
| var method = _type.GetMethod(binder.Name); | ||||||||||
| if (method == null) | ||||||||||
| { | ||||||||||
| result = null; | ||||||||||
| return false; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (!_requestHandlers.TryGetValue(method, out var requestHandler)) | ||||||||||
| { | ||||||||||
| requestHandler = new RequestHandler(method, _context); | ||||||||||
| _requestHandlers[method] = requestHandler; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| result = requestHandler.Send(args); | ||||||||||
| return true; | ||||||||||
| requestHandler = new RequestHandler(method, _context); | ||||||||||
| _requestHandlers[method] = requestHandler; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| result = requestHandler.Send(args); | ||||||||||
| return true; | ||||||||||
|
Comment on lines
+44
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Null-safe handoff of args to RequestHandler.Send(object[])
- result = requestHandler.Send(args);
+ result = requestHandler.Send((args as object[]) ?? Array.Empty<object>());
return true;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| } | ||||||||||
| } | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix overload resolution to avoid AmbiguousMatchException and wrong method dispatch
_type.GetMethod(binder.Name)throws for overloaded methods and ignores argument shapes. Resolve by selecting the best candidate using argument count/types (and allowing a trailing CancellationToken).Apply this diff to use a resolver:
Add this helper inside the class:
And add the needed using:
using System.Reflection; +using System.Threading;🤖 Prompt for AI Agents