Skip to content

Commit

Permalink
Mailtrap API client implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxNau committed May 2, 2024
1 parent 972fa02 commit e560ecb
Show file tree
Hide file tree
Showing 14 changed files with 373 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Mailtrap.NET/ApiHeaderNames.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

namespace Mailtrap.NET
{
internal class ApiHeaderNames
{
internal const string ApiToken = "Api-Token";
}
}
32 changes: 32 additions & 0 deletions Mailtrap.NET/Configuration/MailtrapApiConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;

namespace Mailtrap.NET.Configuration
{
/// <summary>
/// Mailtrap API configuration
/// </summary>
public class MailtrapApiConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="MailtrapApiConfiguration"/> class.
/// </summary>
/// <param name="baseUri"></param>
/// <param name="apiToken"></param>
/// <exception cref="ArgumentNullException"></exception>
public MailtrapApiConfiguration(Uri baseUri, string apiToken)
{
BaseUri = baseUri ?? throw new ArgumentNullException(nameof(baseUri));
ApiToken = apiToken ?? throw new ArgumentNullException(nameof(apiToken));
}

/// <summary>
/// API base uri
/// </summary>
public Uri BaseUri { get; }

/// <summary>
/// API token
/// </summary>
public string ApiToken { get; }
}
}
7 changes: 7 additions & 0 deletions Mailtrap.NET/Data/IQueryString.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Mailtrap.NET.Data
{
internal interface IQueryString
{
string GetQueryString();
}
}
10 changes: 10 additions & 0 deletions Mailtrap.NET/Data/IRequestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Mailtrap.NET.Serializers;
using System.Net.Http;

namespace Mailtrap.NET.Data
{
internal interface IRequestData
{
HttpContent GetContent(ISerializer serializer);
}
}
30 changes: 30 additions & 0 deletions Mailtrap.NET/Data/JsonRequestContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Mailtrap.NET.Contracts.Request;
using Mailtrap.NET.Serializers;
using System;
using System.Net.Http;
using System.Text;

namespace Mailtrap.NET.Data
{
internal class JsonRequestContent<T> : IRequestData where T : IMailtrapRequestBody
{
private const string JsonMimeType = "application/json";

private readonly T _data;

public JsonRequestContent(T data)
{
if (data == null)
{
throw new ArgumentNullException(nameof(data));
}

_data = data;
}

public HttpContent GetContent(ISerializer serializer)
{
return new StringContent(serializer.Serialize(_data), Encoding.UTF8, JsonMimeType);
}
}
}
11 changes: 11 additions & 0 deletions Mailtrap.NET/Endpoints/Constants/EndpointsLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Mailtrap.NET.Endpoints.Constants
{
internal static class EndpointsLocation
{
private const string ApiRoot = "api";
internal static class EmailSending
{
public const string Send = ApiRoot + "/send";
}
}
}
46 changes: 46 additions & 0 deletions Mailtrap.NET/Endpoints/EmailSending.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Mailtrap.NET.Contracts.Request;
using Mailtrap.NET.Contracts.Response;
using Mailtrap.NET.Data;
using Mailtrap.NET.Endpoints.Constants;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Mailtrap.NET.Endpoints
{
/// <summary>
/// Mailtrap Email Sending Endpoint
/// </summary>
public class EmailSending : IEmailSending
{
private readonly MailtrapClient _mailtrapClient;

/// <summary>
/// Initializes a new instance of the <see cref="EmailSending"/> class.
/// </summary>
/// <param name="mailtrapClient"></param>
public EmailSending(MailtrapClient mailtrapClient)
{
_mailtrapClient = mailtrapClient;
}

/// <summary>
/// Send email (text, html, text and html, templates)
/// </summary>
/// <param name="email"></param>
/// <returns>MailtrapResponse</returns>
public async Task<MailtrapResponse> SendAsync(Email email)
{
if (email == null)
{
throw new ArgumentNullException(nameof(email));
}

return await _mailtrapClient.SendAsync(
HttpMethod.Post,
EndpointsLocation.EmailSending.Send,
data: new JsonRequestContent<Email>(email))
.ConfigureAwait(false);
}
}
}
19 changes: 19 additions & 0 deletions Mailtrap.NET/Endpoints/IEmailSending.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Mailtrap.NET.Contracts.Request;
using Mailtrap.NET.Contracts.Response;
using System.Threading.Tasks;

namespace Mailtrap.NET.Endpoints
{
/// <summary>
/// Interface for EmailSending
/// </summary>
public interface IEmailSending
{
/// <summary>
/// Send email (text, html, text and html, templates)
/// </summary>
/// <param name="email"></param>
/// <returns>MailtrapResponse</returns>
Task<MailtrapResponse> SendAsync(Email email);
}
}
15 changes: 15 additions & 0 deletions Mailtrap.NET/IMailtrapClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Mailtrap.NET.Endpoints;

namespace Mailtrap.NET
{
/// <summary>
/// Mailtrap API client interface
/// </summary>
public interface IMailtrapClient
{
/// <summary>
/// EmailSending endpoint
/// </summary>
IEmailSending EmailSending { get; set; }
}
}
4 changes: 4 additions & 0 deletions Mailtrap.NET/Mailtrap.NET.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="8.0.3" />
</ItemGroup>

</Project>
115 changes: 115 additions & 0 deletions Mailtrap.NET/MailtrapClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using Mailtrap.NET.Configuration;
using Mailtrap.NET.Contracts.Response;
using Mailtrap.NET.Data;
using Mailtrap.NET.Endpoints;
using Mailtrap.NET.Serializers;
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Mailtrap.NET
{
/// <summary>
/// Mailtrap API client
/// </summary>
public sealed class MailtrapClient : IMailtrapClient, IDisposable
{
private readonly HttpClient _httpClient;
private readonly MailtrapApiConfiguration _configuration;
private readonly ISerializer _serializer;

/// <summary>
/// Initializes a new instance of the <see cref="MailtrapClient"/> class.
/// </summary>
/// <param name="httpClient"></param>
/// <param name="configuration"></param>
/// <param name="serializer"></param>
/// <exception cref="ArgumentNullException"></exception>
public MailtrapClient(HttpClient httpClient, MailtrapApiConfiguration configuration, ISerializer serializer = null)

{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));

_serializer = serializer ?? DefaultSerializer.Serializer;

_httpClient.DefaultRequestHeaders.Add(ApiHeaderNames.ApiToken, configuration.ApiToken);

EmailSending = new EmailSending(this);
}

/// <summary>
/// Email Sending client
/// </summary>
public IEmailSending EmailSending { get; set; }

internal async Task<MailtrapResponse> SendAsync(HttpMethod httpMethod, string path, IQueryString queryString = default, IRequestData data = default)
{
if (httpMethod == null)
{
throw new ArgumentNullException(nameof(httpMethod));
}

if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentNullException(nameof(path));
}

if (httpMethod == HttpMethod.Post && data == null)
{
throw new ArgumentNullException(nameof(data));
}

return await SendInternalAsync(httpMethod, path, queryString, data).ConfigureAwait(false);
}

private async Task<MailtrapResponse> SendInternalAsync(HttpMethod httpMethod, string path, IQueryString queryString, IRequestData data)
{
var requestMessage = CreateRequest(httpMethod, path, queryString, data);
var response = await _httpClient.SendAsync(requestMessage).ConfigureAwait(false);

return await _serializer.DeserializeAsync<MailtrapResponse>(await response.Content.ReadAsStreamAsync())
.ConfigureAwait(false);
}

private HttpRequestMessage CreateRequest(HttpMethod httpMethod, string path, IQueryString queryString, IRequestData data)
{
var requestMessage = new HttpRequestMessage(httpMethod, BuildUri(_configuration.BaseUri, path, queryString))
{
Content = data.GetContent(_serializer),
};

return requestMessage;
}

private Uri BuildUri(Uri baseUri, string path, IQueryString queryString)
{
if (baseUri == null)
{
throw new ArgumentNullException(nameof(baseUri));
}

var builder = new UriBuilder(baseUri);

if (!string.IsNullOrWhiteSpace(path))
{
builder.Path += path;
}

if (queryString != null)
{
builder.Query = queryString.GetQueryString();
}

return builder.Uri;
}

/// <summary>
/// Releases the unmanaged recources of underlying objects
/// </summary>
public void Dispose()
{
_httpClient.Dispose();
}
}
}
24 changes: 24 additions & 0 deletions Mailtrap.NET/Serializers/DefaultSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Mailtrap.NET.Serializers
{
/// <summary>
/// DefaultSerializer
/// </summary>
public static class DefaultSerializer
{
private static readonly JsonSerializerOptions _options;

static DefaultSerializer()
{
_options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
_options.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseLower, false));
}

/// <summary>
/// Reutrns new instance of TextJsonSerializer
/// </summary>
public static ISerializer Serializer => new TextJsonSerializer(_options);
}
}
27 changes: 27 additions & 0 deletions Mailtrap.NET/Serializers/ISerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.IO;
using System.Threading.Tasks;

namespace Mailtrap.NET.Serializers
{
/// <summary>
/// Message serializer interface
/// </summary>
public interface ISerializer
{
/// <summary>
/// Serializes object to string
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="object"></param>
/// <returns></returns>
string Serialize<T>(T @object);

/// <summary>
/// Deserializes stream into provided type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="stream"></param>
/// <returns></returns>
ValueTask<T> DeserializeAsync<T>(Stream stream);
}
}
25 changes: 25 additions & 0 deletions Mailtrap.NET/Serializers/TextJsonSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;

namespace Mailtrap.NET.Serializers
{
internal class TextJsonSerializer : ISerializer
{
private readonly JsonSerializerOptions _options;
public TextJsonSerializer(JsonSerializerOptions options)
{
_options = options;
}

public async ValueTask<T> DeserializeAsync<T>(Stream stream)
{
return await JsonSerializer.DeserializeAsync<T>(stream, _options).ConfigureAwait(false);
}

public string Serialize<T>(T @object)
{
return JsonSerializer.Serialize(@object, @object.GetType(), _options);
}
}
}

0 comments on commit e560ecb

Please sign in to comment.