Skip to content
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

Make it possible to create a new client based on an existing client. #40

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Activout.RestClient.Test.MovieReviews
{
[Path("movies")]
[ErrorResponse(typeof(ErrorResponse))]
public interface IMovieReviewService
public interface IMovieReviewService : IExtendable
{
[Get]
Task<IEnumerable<Movie>> GetAllMovies();
Expand Down
34 changes: 34 additions & 0 deletions Activout.RestClient.Test/RestClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,40 @@ private IMovieReviewService CreateMovieReviewService()
.Build<IMovieReviewService>();
}

[Fact]
public async Task TestExtendClient()
{
// arrange
_mockHttp
.Expect($"{BaseUri}/movies/string")
.WithHeaders("accept", "text/plain")
.WithHeaders("Authorization", "Bearer 111")
.Respond(new StringContent("foo"));

_mockHttp
.Expect($"{BaseUri}/movies/string")
.WithHeaders("accept", "text/plain")
.WithHeaders("Authorization", "Bearer 222")
.Respond(new StringContent("bar"));

var movieReviewService1 = CreateRestClientBuilder()
.Header(new AuthenticationHeaderValue("Bearer", "111"))
.Build<IMovieReviewService>();

var movieReviewService2 = _restClientFactory
.Extend(movieReviewService1)
.Header(new AuthenticationHeaderValue("Bearer", "222"))
.Build<IMovieReviewService>();

// act
var result1 = await movieReviewService1.GetString();
var result2 = await movieReviewService2.GetString();

// assert
Assert.Equal("foo", result1);
Assert.Equal("bar", result2);
}

[Fact]
public async Task TestErrorAsyncWithOldTaskConverter()
{
Expand Down
8 changes: 8 additions & 0 deletions Activout.RestClient/IExtendable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Activout.RestClient;

public interface IExtendableContext;

public interface IExtendable
{
IExtendableContext ExtendableContext { get; }
}
2 changes: 1 addition & 1 deletion Activout.RestClient/IRestClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public interface IRestClientBuilder
{
IRestClientBuilder BaseUri(Uri apiUri);
IRestClientBuilder ContentType(MediaType contentType);
IRestClientBuilder Header(string name, object value);
IRestClientBuilder Header(string name, object value, bool replace = false);
IRestClientBuilder With(ILogger logger);
IRestClientBuilder With(HttpClient httpClient);
IRestClientBuilder With(IRequestLogger requestLogger);
Expand Down
10 changes: 5 additions & 5 deletions Activout.RestClient/IRestClientFactory.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Activout.RestClient
namespace Activout.RestClient;

public interface IRestClientFactory
{
public interface IRestClientFactory
{
IRestClientBuilder CreateBuilder();
}
IRestClientBuilder CreateBuilder();
IRestClientBuilder Extend(IExtendable client);
}
5 changes: 5 additions & 0 deletions Activout.RestClient/Implementation/ExtendableContextImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using System;

namespace Activout.RestClient.Implementation;

internal record ExtendableContextImpl(RestClientContext Context, Type Type) : IExtendableContext;
10 changes: 5 additions & 5 deletions Activout.RestClient/Implementation/RestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@

namespace Activout.RestClient.Implementation
{
internal class RestClient<T> : DynamicObject where T : class
internal class RestClient<T> : DynamicObject, IExtendable where T : class
{
private readonly RestClientContext _context;
private readonly Type _type;

private readonly IDictionary<MethodInfo, RequestHandler> _requestHandlers =
new ConcurrentDictionary<MethodInfo, RequestHandler>();

private readonly ConcurrentDictionary<MethodInfo, RequestHandler> _requestHandlers = new();

public RestClient(RestClientContext context)
{
_type = typeof(T);
_context = context;
HandleAttributes();
_context.DefaultSerializer = _context.SerializationManager.GetSerializer(_context.DefaultContentType);
}

public IExtendableContext ExtendableContext => new ExtendableContextImpl(_context, _type);

private void HandleAttributes()
{
Expand Down
14 changes: 13 additions & 1 deletion Activout.RestClient/Implementation/RestClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ public RestClientBuilder(
};
}

internal RestClientBuilder(
IDuckTyping duckTyping,
RestClientContext context)
{
_duckTyping = duckTyping ?? throw new ArgumentNullException(nameof(duckTyping));
_context = context with { DefaultHeaders = [..context.DefaultHeaders] };
}

public IRestClientBuilder BaseUri(Uri apiUri)
{
Expand All @@ -47,8 +54,13 @@ public IRestClientBuilder ContentType(MediaType contentType)
return this;
}

public IRestClientBuilder Header(string name, object value)
public IRestClientBuilder Header(string name, object value, bool replace = false)
{
if (replace)
{
_context.DefaultHeaders.RemoveAll(h => h.Key == name);
}

_context.DefaultHeaders.Add(new KeyValuePair<string, object>(name, value));
return this;
}
Expand Down
23 changes: 6 additions & 17 deletions Activout.RestClient/Implementation/RestClientContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,21 @@

namespace Activout.RestClient.Implementation
{
internal class RestClientContext
internal record RestClientContext
{
private static readonly Collection<MediaType> JsonMediaTypeCollection = new Collection<MediaType>
{
MediaType.ValueOf("application/json")
};

public RestClientContext()
{
BaseTemplate = "";
DefaultContentType = JsonMediaTypeCollection.FirstOrDefault();
DefaultHeaders = new List<KeyValuePair<string, object>>();
ErrorResponseType = typeof(string);
}
private static readonly Collection<MediaType> JsonMediaTypeCollection = [MediaType.ValueOf("application/json")];

public ILogger Logger { get; internal set; } = NullLogger.Instance;
public Uri BaseUri { get; internal set; }
public string BaseTemplate { get; internal set; }
public string BaseTemplate { get; internal set; } = "";
public ISerializer DefaultSerializer { get; internal set; }
public ISerializationManager SerializationManager { get; internal set; }
public HttpClient HttpClient { get; internal set; }
public ITaskConverterFactory TaskConverterFactory { get; internal set; }
public Type ErrorResponseType { get; internal set; }
public MediaType DefaultContentType { get; internal set; }
public Type ErrorResponseType { get; internal set; } = typeof(string);
public MediaType DefaultContentType { get; internal set; } = JsonMediaTypeCollection.FirstOrDefault();
public IParamConverterManager ParamConverterManager { get; internal set; }
public List<KeyValuePair<string, object>> DefaultHeaders { get; }
public List<KeyValuePair<string, object>> DefaultHeaders { get; set; } = [];
public IRequestLogger RequestLogger { get; set; } = new DummyRequestLogger();
public Type DomainExceptionType { get; set; }
public IDomainExceptionMapperFactory DomainExceptionMapperFactory { get; set; }
Expand Down
13 changes: 12 additions & 1 deletion Activout.RestClient/Implementation/RestClientFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Activout.RestClient.Helpers;
using System;
using Activout.RestClient.Helpers;
using Activout.RestClient.ParamConverter;

namespace Activout.RestClient.Implementation
Expand All @@ -23,5 +24,15 @@ public IRestClientBuilder CreateBuilder()
{
return new RestClientBuilder(_duckTyping, _paramConverterManager, _taskConverterFactory);
}

public IRestClientBuilder Extend(IExtendable client)
{
if (client.ExtendableContext is not ExtendableContextImpl extendableContext)
{
throw new InvalidOperationException("Client must be created by Activout.RestClient");
}

return new RestClientBuilder(_duckTyping, extendableContext.Context);
}
}
}
4 changes: 2 additions & 2 deletions Activout.RestClient/RestClientBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ public static IRestClientBuilder ContentType(this IRestClientBuilder self, strin

public static IRestClientBuilder Accept(this IRestClientBuilder self, string accept)
{
return self.Header("Accept", accept);
return self.Header("Accept", accept, true);
}

public static IRestClientBuilder Header(this IRestClientBuilder self,
AuthenticationHeaderValue authenticationHeaderValue)
{
return self.Header("Authorization", authenticationHeaderValue);
return self.Header("Authorization", authenticationHeaderValue, true);
}
}
}
Loading