diff options
Diffstat (limited to 'Emails.Transactional.Client')
12 files changed, 835 insertions, 0 deletions
diff --git a/Emails.Transactional.Client/ClientExtensions.cs b/Emails.Transactional.Client/ClientExtensions.cs new file mode 100644 index 0000000..3246b9f --- /dev/null +++ b/Emails.Transactional.Client/ClientExtensions.cs @@ -0,0 +1,55 @@ +using System; + +using System.Net; +using System.Threading; +using System.Threading.Tasks; + +using Emails.Transactional.Client.Exceptions; + +using RestSharp; + +namespace Emails.Transactional.Client +{ + /// <summary> + /// Contains extension methods to send emails on remote transational email servers + /// </summary> + public static class ClientExtensions + { + private const Method DEFAULT_SEND_METHOD = Method.Post; + + /// <summary> + /// Asynchronously begins an email transaction against the mail server with the specified + /// transaction request. + /// </summary> + /// <param name="client"></param> + /// <param name="transaction">The <see cref="EmailTransactionRequest"/> to submit</param> + /// <param name="token">A cancelaion token to cancel the operation</param> + /// <returns>A task that represents the async send operation</returns> + /// <exception cref="ValidationFailedException"></exception> + /// <exception cref="InvalidAuthorizationException"></exception> + /// <exception cref="InvalidTransactionRequestException"></exception> + /// <exception cref="InvalidTransactionResponseException"></exception> + public static async Task<TransactionResult> SendEmailAsync(this RestClient client, EmailTransactionRequest transaction, CancellationToken token = default) + { + //Init the new request + RestRequest request = new(transaction.Endpoint, DEFAULT_SEND_METHOD) + { + //Json request + RequestFormat = DataFormat.Json + }; + //add/serialze the transacion request + request.AddJsonBody(transaction); + //Exec the tranasction on the client + RestResponse<TransactionResult> response = await client.ExecuteAsync<TransactionResult>(request, token); + //parse the response body + return response.StatusCode switch + { + HttpStatusCode.Unauthorized or HttpStatusCode.Forbidden => throw new InvalidAuthorizationException("The server did not accept the current authorization", response), + HttpStatusCode.BadRequest => throw new InvalidTransactionRequestException(response), + HttpStatusCode.UnprocessableEntity => throw new ValidationFailedException(response.Data), + HttpStatusCode.OK => response.Data, + _ => throw new InvalidTransactionResponseException("Unhandled status code", response), + }; + } + } +} diff --git a/Emails.Transactional.Client/EmailTransactionRequest.cs b/Emails.Transactional.Client/EmailTransactionRequest.cs new file mode 100644 index 0000000..1f1a4e2 --- /dev/null +++ b/Emails.Transactional.Client/EmailTransactionRequest.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emails.Transactional.Client +{ + /// <summary> + /// A transactional email request to send an email + /// template. + /// </summary> + public class EmailTransactionRequest + { + /// <summary> + /// The transactional send endpoint address + /// </summary> + [JsonIgnore] + public Uri Endpoint { get; init; } + /// <summary> + /// A dictionary of email addresses/names of + /// users to send this email to + /// </summary> + [JsonPropertyName("to")] + public Dictionary<string, string> ToAddresses { get; set; } + /// <summary> + /// A dictionary of email addresses/names of + /// users to carbon copy this email to + /// </summary> + [JsonPropertyName("cc")] + public Dictionary<string, string> CcAddresses { get; set; } + /// <summary> + /// A dictionary of email addresses/names of + /// users to blind carbon copy this email to + /// </summary> + [JsonPropertyName("bcc")] + public Dictionary<string, string> BccAddresses { get; set; } + /// <summary> + /// A dictionary of variables to substitute into the liquid + /// email template + /// </summary> + [JsonPropertyName("variables")] + public Dictionary<string, string> Variables { get; set; } + + /// <summary> + /// The subject of the email to send + /// </summary> + [JsonPropertyName("subject")] + public string Subject { get; set; } + + /// <summary> + /// The unique id of the email template to send + /// </summary> + [JsonPropertyName("template_id")] + public string TemplateId { get; set; } + /// <summary> + /// The system from email name. NOTE: This is a protected value + /// </summary> + [JsonPropertyName("from_name")] + public string FromName { get; set; } + /// <summary> + /// The system from email address. NOTE: This is a protected value + /// </summary> + [JsonPropertyName("from_address")] + public string FromAddress { get; set; } + + /// <summary> + /// Creates a new email transaction with the specified email template to send + /// </summary> + /// <param name="templateId">The id of the template to send</param> + /// <exception cref="ArgumentNullException"></exception> + public EmailTransactionRequest(string templateId) + { + this.TemplateId = templateId ?? throw new ArgumentNullException(nameof(templateId)); + } + /// <summary> + /// Creates a new email transaction with the specified email template + /// and a single recipient + /// </summary> + /// <param name="templateId">The id of the template to send</param> + /// <param name="toAddress">A singular recipient name</param> + /// <param name="toName">A singlular recipient email address</param> + /// <exception cref="ArgumentNullException"></exception> + public EmailTransactionRequest(string templateId, string toName, string toAddress) + { + this.TemplateId = templateId ?? throw new ArgumentNullException(nameof(templateId)); + AddToAddress(toName, toAddress); + } + /// <summary> + /// Adds a recipient to the To email address dictionary + /// </summary> + /// <param name="toName">The name of the user to send the email to</param> + /// <param name="toAddress">The unique email address of the user to add to the recipient collection</param> + public void AddToAddress(string toName, string toAddress) + { + ToAddresses ??= new(1); + ToAddresses.Add(toAddress, toName); + } + /// <summary> + /// Adds a recipient to the To email address dictionary + /// </summary> + /// <param name="toAddress">The unique email address of the user to add to the recipient collection</param> + public void AddToAddress(string toAddress) + { + string name = toAddress.Split('@')[0]; + AddToAddress(name, toAddress); + } + /// <summary> + /// Adds a carbon copy recipient to the current cc dictionary + /// </summary> + /// <param name="ccName">The name of the recipient</param> + /// <param name="ccAddress">The unique email address of the bcc recipient</param> + public void AddCcAddress(string ccName, string ccAddress) + { + CcAddresses ??= new(1); + CcAddresses.Add(ccAddress, ccName); + } + /// <summary> + /// Adds a blind carbon copy recipient to the current bcc dictionary + /// </summary> + /// <param name="bccName">The name of the recipient</param> + /// <param name="bccAddress">The unique email address of the bcc recipient</param> + public void AddBccAddress(string bccName, string bccAddress) + { + BccAddresses ??= new(1); + BccAddresses.Add(bccAddress, bccName); + } + /// <summary> + /// Adds a liquid template variable to be subsituted by the template + /// renderer. + /// </summary> + /// <param name="varName">The unique name of the variable to add to the collection</param> + /// <param name="varValue">The value if the variable that will be substituted into the template</param> + public void AddVariable(string varName, string varValue) + { + Variables ??= new(1); + Variables.Add(varName, varValue); + } + } +} diff --git a/Emails.Transactional.Client/Emails.Transactional.Client.csproj b/Emails.Transactional.Client/Emails.Transactional.Client.csproj new file mode 100644 index 0000000..e39cb07 --- /dev/null +++ b/Emails.Transactional.Client/Emails.Transactional.Client.csproj @@ -0,0 +1,33 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <Authors>Vaughn Nugent</Authors> + <Version>1.0.0.1</Version> + <Description>A client library for using the Emails.Transactional server plugin</Description> + <PackageProjectUrl>www.vaughnnugent.com/resources</PackageProjectUrl> + <Copyright>Copyright © 2022 Vaughn Nugent</Copyright> + <Platforms>AnyCPU;x64</Platforms> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> + <DocumentationFile></DocumentationFile> + </PropertyGroup> + + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <DocumentationFile></DocumentationFile> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + <PackageReference Include="RestSharp" Version="108.0.2" /> + </ItemGroup> + +</Project> diff --git a/Emails.Transactional.Client/Emails.Transactional.Client.xml b/Emails.Transactional.Client/Emails.Transactional.Client.xml new file mode 100644 index 0000000..013b85e --- /dev/null +++ b/Emails.Transactional.Client/Emails.Transactional.Client.xml @@ -0,0 +1,308 @@ +<?xml version="1.0"?> +<doc> + <assembly> + <name>Emails.Transactional.Client</name> + </assembly> + <members> + <member name="T:Emails.Transactional.Client.ClientExtensions"> + <summary> + Contains extension methods to send emails on remote transational email servers + </summary> + </member> + <member name="M:Emails.Transactional.Client.ClientExtensions.SendEmailAsync(RestSharp.RestClient,Emails.Transactional.Client.EmailTransactionRequest,System.Threading.CancellationToken)"> + <summary> + Asynchronously begins an email transaction against the mail server with the specified + transaction request. + </summary> + <param name="client"></param> + <param name="transaction">The <see cref="T:Emails.Transactional.Client.EmailTransactionRequest"/> to submit</param> + <param name="token">A cancelaion token to cancel the operation</param> + <returns>A task that represents the async send operation</returns> + <exception cref="T:Emails.Transactional.Client.Exceptions.ValidationFailedException"></exception> + <exception cref="T:Emails.Transactional.Client.Exceptions.InvalidAuthorizationException"></exception> + <exception cref="T:Emails.Transactional.Client.Exceptions.InvalidTransactionRequestException"></exception> + <exception cref="T:Emails.Transactional.Client.Exceptions.InvalidTransactionResponseException"></exception> + </member> + <member name="T:Emails.Transactional.Client.EmailTransactionRequest"> + <summary> + A transactional email request to send an email + template. + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.Endpoint"> + <summary> + The transactional send endpoint address + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.ToAddresses"> + <summary> + A dictionary of email addresses/names of + users to send this email to + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.CcAddresses"> + <summary> + A dictionary of email addresses/names of + users to carbon copy this email to + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.BccAddresses"> + <summary> + A dictionary of email addresses/names of + users to blind carbon copy this email to + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.Variables"> + <summary> + A dictionary of variables to substitute into the liquid + email template + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.Subject"> + <summary> + The subject of the email to send + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.TemplateId"> + <summary> + The unique id of the email template to send + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.FromName"> + <summary> + The system from email name. NOTE: This is a protected value + </summary> + </member> + <member name="P:Emails.Transactional.Client.EmailTransactionRequest.FromAddress"> + <summary> + The system from email address. NOTE: This is a protected value + </summary> + </member> + <member name="M:Emails.Transactional.Client.EmailTransactionRequest.#ctor(System.String)"> + <summary> + Creates a new email transaction with the specified email template to send + </summary> + <param name="templateId">The id of the template to send</param> + <exception cref="T:System.ArgumentNullException"></exception> + </member> + <member name="M:Emails.Transactional.Client.EmailTransactionRequest.#ctor(System.String,System.String,System.String)"> + <summary> + Creates a new email transaction with the specified email template + and a single recipient + </summary> + <param name="templateId">The id of the template to send</param> + <param name="toAddress">A singular recipient name</param> + <param name="toName">A singlular recipient email address</param> + <exception cref="T:System.ArgumentNullException"></exception> + </member> + <member name="M:Emails.Transactional.Client.EmailTransactionRequest.AddToAddress(System.String,System.String)"> + <summary> + Adds a recipient to the To email address dictionary + </summary> + <param name="toName">The name of the user to send the email to</param> + <param name="toAddress">The unique email address of the user to add to the recipient collection</param> + </member> + <member name="M:Emails.Transactional.Client.EmailTransactionRequest.AddToAddress(System.String)"> + <summary> + Adds a recipient to the To email address dictionary + </summary> + <param name="toAddress">The unique email address of the user to add to the recipient collection</param> + </member> + <member name="M:Emails.Transactional.Client.EmailTransactionRequest.AddCcAddress(System.String,System.String)"> + <summary> + Adds a carbon copy recipient to the current cc dictionary + </summary> + <param name="ccName">The name of the recipient</param> + <param name="ccAddress">The unique email address of the bcc recipient</param> + </member> + <member name="M:Emails.Transactional.Client.EmailTransactionRequest.AddBccAddress(System.String,System.String)"> + <summary> + Adds a blind carbon copy recipient to the current bcc dictionary + </summary> + <param name="bccName">The name of the recipient</param> + <param name="bccAddress">The unique email address of the bcc recipient</param> + </member> + <member name="M:Emails.Transactional.Client.EmailTransactionRequest.AddVariable(System.String,System.String)"> + <summary> + Adds a liquid template variable to be subsituted by the template + renderer. + </summary> + <param name="varName">The unique name of the variable to add to the collection</param> + <param name="varValue">The value if the variable that will be substituted into the template</param> + </member> + <member name="T:Emails.Transactional.Client.Exceptions.InvalidAuthorizationException"> + <summary> + A excption raised when an Authorization error occured + during a request + </summary> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidAuthorizationException.#ctor"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidAuthorizationException.#ctor(System.String)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidAuthorizationException.#ctor(System.String,System.Exception)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidAuthorizationException.#ctor(System.String,RestSharp.RestResponse{Emails.Transactional.Client.TransactionResult})"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidTransactionRequestException.#ctor"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidTransactionRequestException.#ctor(System.String)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidTransactionRequestException.#ctor(System.String,System.Exception)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidTransactionRequestException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)"> + <inheritdoc/> + </member> + <member name="T:Emails.Transactional.Client.Exceptions.InvalidTransactionResponseException"> + <summary> + Raised when the results of an email transaction + failed. Inner exceptions may be set + </summary> + </member> + <member name="P:Emails.Transactional.Client.Exceptions.InvalidTransactionResponseException.ErrorMessage"> + <summary> + An error message received from the client + </summary> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidTransactionResponseException.#ctor"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidTransactionResponseException.#ctor(System.String)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidTransactionResponseException.#ctor(System.String,System.Exception)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.InvalidTransactionResponseException.#ctor(System.String,RestSharp.RestResponse{Emails.Transactional.Client.TransactionResult})"> + <summary> + Initializes a new <see cref="T:Emails.Transactional.Client.Exceptions.InvalidTransactionResponseException"/> with + the response that contains the error + </summary> + <param name="message">The base exception message</param> + <param name="response">The response that caused the error</param> + </member> + <member name="T:Emails.Transactional.Client.Exceptions.TransactionExceptionBase"> + <summary> + A base exception for all client transaction excepions + </summary> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.TransactionExceptionBase.#ctor"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.TransactionExceptionBase.#ctor(System.String)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.TransactionExceptionBase.#ctor(System.String,System.Exception)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.TransactionExceptionBase.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)"> + <inheritdoc/> + </member> + <member name="P:Emails.Transactional.Client.Exceptions.TransactionExceptionBase.ErrorResponse"> + <summary> + The response objec that caused the exception + </summary> + </member> + <member name="P:Emails.Transactional.Client.Exceptions.TransactionExceptionBase.ResultMessage"> + <summary> + The string represenation of the response body + </summary> + </member> + <member name="T:Emails.Transactional.Client.Exceptions.ValidationFailedException"> + <summary> + Raised when server message validation failed + </summary> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.ValidationFailedException.#ctor"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.ValidationFailedException.#ctor(System.String)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.ValidationFailedException.#ctor(System.String,System.Exception)"> + <inheritdoc/> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.ValidationFailedException.#ctor(System.Runtime.Serialization.SerializationInfo,System.Runtime.Serialization.StreamingContext)"> + <inheritdoc/> + </member> + <member name="P:Emails.Transactional.Client.Exceptions.ValidationFailedException.ValidationErrors"> + <summary> + A collection of validaion error messages + </summary> + </member> + <member name="M:Emails.Transactional.Client.Exceptions.ValidationFailedException.#ctor(Emails.Transactional.Client.TransactionResult)"> + <summary> + + </summary> + <param name="result"></param> + </member> + <member name="T:Emails.Transactional.Client.TransactionalEmailConfig"> + <summary> + A global configuration object for transactional email clients + </summary> + </member> + <member name="P:Emails.Transactional.Client.TransactionalEmailConfig.ServiceLocation"> + <summary> + The server transaction endpoint location + </summary> + </member> + <member name="P:Emails.Transactional.Client.TransactionalEmailConfig.TemplateIdLookup"> + <summary> + An email id template/translation table for email template-ids + </summary> + </member> + <member name="M:Emails.Transactional.Client.TransactionalEmailConfig.WithUrl(System.Uri)"> + <summary> + Adds the mail service location to the current instance + </summary> + <param name="serviceLocation">The address of the remote server transaction endpoint</param> + <returns>A referrence to the current object (fluent api)</returns> + <exception cref="T:System.ArgumentNullException"></exception> + </member> + <member name="M:Emails.Transactional.Client.TransactionalEmailConfig.WithTemplates(System.Collections.Generic.IReadOnlyDictionary{System.String,System.String})"> + <summary> + Sets the template lookup table for the current instance + </summary> + <param name="templates">The template-id lookup table to referrence</param> + <returns>A referrence to the current object (fluent api)</returns> + <exception cref="T:System.ArgumentNullException"></exception> + </member> + <member name="M:Emails.Transactional.Client.TransactionalEmailConfig.GetTemplateRequest(System.String)"> + <summary> + Gets a new <see cref="T:Emails.Transactional.Client.EmailTransactionRequest"/> from the specifed + template name. + </summary> + <param name="templateName"></param> + <returns></returns> + <exception cref="T:System.Collections.Generic.KeyNotFoundException"></exception> + <exception cref="T:System.ArgumentNullException"></exception> + </member> + <member name="T:Emails.Transactional.Client.TransactionResult"> + <summary> + A JSON serializable object that contains the results of the transaction + </summary> + </member> + <member name="T:Emails.Transactional.Client.ValidationErrorMessage"> + <summary> + A json serializable server validaion error + </summary> + </member> + <member name="P:Emails.Transactional.Client.ValidationErrorMessage.PropertyName"> + <summary> + The name of the propery that was invalid + </summary> + </member> + <member name="P:Emails.Transactional.Client.ValidationErrorMessage.ErrorMessage"> + <summary> + The message that + </summary> + </member> + </members> +</doc> diff --git a/Emails.Transactional.Client/Exceptions/InvalidAuthorizationException.cs b/Emails.Transactional.Client/Exceptions/InvalidAuthorizationException.cs new file mode 100644 index 0000000..eed2f42 --- /dev/null +++ b/Emails.Transactional.Client/Exceptions/InvalidAuthorizationException.cs @@ -0,0 +1,26 @@ +using System; + +using RestSharp; + +namespace Emails.Transactional.Client.Exceptions +{ + /// <summary> + /// A excption raised when an Authorization error occured + /// during a request + /// </summary> + public class InvalidAuthorizationException : InvalidTransactionResponseException + { + ///<inheritdoc/> + public InvalidAuthorizationException():base() + {} + ///<inheritdoc/> + public InvalidAuthorizationException(string message) : base(message) + {} + ///<inheritdoc/> + public InvalidAuthorizationException(string message, Exception innerException) : base(message, innerException) + {} + ///<inheritdoc/> + public InvalidAuthorizationException(string message, RestResponse<TransactionResult> response) : base(message, response) + {} + } +} diff --git a/Emails.Transactional.Client/Exceptions/InvalidTransactionRequestException.cs b/Emails.Transactional.Client/Exceptions/InvalidTransactionRequestException.cs new file mode 100644 index 0000000..11d4bd9 --- /dev/null +++ b/Emails.Transactional.Client/Exceptions/InvalidTransactionRequestException.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.Serialization; + +using RestSharp; + +namespace Emails.Transactional.Client.Exceptions +{ + public class InvalidTransactionRequestException : TransactionExceptionBase + { + ///<inheritdoc/> + public InvalidTransactionRequestException() + {} + ///<inheritdoc/> + public InvalidTransactionRequestException(string message) : base(message) + {} + ///<inheritdoc/> + public InvalidTransactionRequestException(string message, Exception innerException) : base(message, innerException) + {} + ///<inheritdoc/> + protected InvalidTransactionRequestException(SerializationInfo info, StreamingContext context) : base(info, context) + {} + + public InvalidTransactionRequestException(RestResponse response) + { + this.ErrorResponse = response; + } + } +} diff --git a/Emails.Transactional.Client/Exceptions/InvalidTransactionResponseException.cs b/Emails.Transactional.Client/Exceptions/InvalidTransactionResponseException.cs new file mode 100644 index 0000000..ed64a0c --- /dev/null +++ b/Emails.Transactional.Client/Exceptions/InvalidTransactionResponseException.cs @@ -0,0 +1,40 @@ +using System; +using RestSharp; + +namespace Emails.Transactional.Client.Exceptions +{ + /// <summary> + /// Raised when the results of an email transaction + /// failed. Inner exceptions may be set + /// </summary> + public class InvalidTransactionResponseException : TransactionExceptionBase + { + /// <summary> + /// An error message received from the client + /// </summary> + public TransactionResult ErrorMessage { get; init; } + + ///<inheritdoc/> + public InvalidTransactionResponseException() + {} + ///<inheritdoc/> + public InvalidTransactionResponseException(string message) : base(message) + {} + ///<inheritdoc/> + public InvalidTransactionResponseException(string message, Exception innerException) : base(message, innerException) + {} + + /// <summary> + /// Initializes a new <see cref="InvalidTransactionResponseException"/> with + /// the response that contains the error + /// </summary> + /// <param name="message">The base exception message</param> + /// <param name="response">The response that caused the error</param> + public InvalidTransactionResponseException(string message, RestResponse<TransactionResult> response) : base(message, response.ErrorException) + { + this.ErrorResponse = response; + //See if the server sent an error message + this.ErrorMessage = response.Data; + } + } +} diff --git a/Emails.Transactional.Client/Exceptions/TransactionExceptionBase.cs b/Emails.Transactional.Client/Exceptions/TransactionExceptionBase.cs new file mode 100644 index 0000000..6cee30d --- /dev/null +++ b/Emails.Transactional.Client/Exceptions/TransactionExceptionBase.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.Serialization; + +using RestSharp; + +namespace Emails.Transactional.Client.Exceptions +{ + /// <summary> + /// A base exception for all client transaction excepions + /// </summary> + public class TransactionExceptionBase : Exception + { + ///<inheritdoc/> + public TransactionExceptionBase() + {} + ///<inheritdoc/> + public TransactionExceptionBase(string message) : base(message) + {} + ///<inheritdoc/> + public TransactionExceptionBase(string message, Exception innerException) : base(message, innerException) + {} + ///<inheritdoc/> + protected TransactionExceptionBase(SerializationInfo info, StreamingContext context) : base(info, context) + {} + + /// <summary> + /// The response objec that caused the exception + /// </summary> + public RestResponse ErrorResponse { get; init; } + /// <summary> + /// The string represenation of the response body + /// </summary> + public string ResultMessage => ErrorResponse.Content; + } +}
\ No newline at end of file diff --git a/Emails.Transactional.Client/Exceptions/ValidationFailedException.cs b/Emails.Transactional.Client/Exceptions/ValidationFailedException.cs new file mode 100644 index 0000000..12bcc33 --- /dev/null +++ b/Emails.Transactional.Client/Exceptions/ValidationFailedException.cs @@ -0,0 +1,62 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Text; + +#nullable enable + +namespace Emails.Transactional.Client.Exceptions +{ + /// <summary> + /// Raised when server message validation failed + /// </summary> + public class ValidationFailedException : InvalidTransactionRequestException + { + ///<inheritdoc/> + public ValidationFailedException() + {} + ///<inheritdoc/> + public ValidationFailedException(string message) : base(message) + {} + ///<inheritdoc/> + public ValidationFailedException(string message, Exception innerException) : base(message, innerException) + {} + + /// <summary> + /// A collection of validaion error messages + /// </summary> + public ICollection<ValidationErrorMessage>? ValidationErrors { get; init; } + /// <summary> + /// + /// </summary> + /// <param name="result"></param> + public ValidationFailedException(TransactionResult result):base("Transaction data server validation failed") + { + this.ValidationErrors = result?.ValidationErrors; + } + + ///<inheritdoc/> + public override string Message + { + get + { + if(ValidationErrors == null) + { + return base.Message; + + } + StringBuilder sb = new(base.Message); + sb.AppendLine(); + + foreach(var kvp in ValidationErrors) + { + sb.Append("Validation error: "); + sb.Append(kvp.PropertyName); + sb.Append(' '); + sb.AppendLine(kvp.ErrorMessage); + } + return sb.ToString(); + } + } + } +} diff --git a/Emails.Transactional.Client/TransactionResult.cs b/Emails.Transactional.Client/TransactionResult.cs new file mode 100644 index 0000000..5c97b3d --- /dev/null +++ b/Emails.Transactional.Client/TransactionResult.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Emails.Transactional.Client +{ + /// <summary> + /// A JSON serializable object that contains the results of the transaction + /// </summary> + public class TransactionResult + { + [JsonPropertyName("transaction_id")] + public string TransactionId { get; set; } + [JsonPropertyName("smtp_status")] + public string SmtpStatus { get; set; } + [JsonPropertyName("success")] + public bool Success { get; set; } + + [JsonPropertyName("error_code")] + public string ErrorCode { get; set; } + + [JsonPropertyName("error_description")] + public string ErrorDescription { get; set; } + + [JsonPropertyName("errors")] + public ICollection<ValidationErrorMessage> ValidationErrors { get; set; } + } +} diff --git a/Emails.Transactional.Client/TransactionalEmailConfig.cs b/Emails.Transactional.Client/TransactionalEmailConfig.cs new file mode 100644 index 0000000..9efeba4 --- /dev/null +++ b/Emails.Transactional.Client/TransactionalEmailConfig.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace Emails.Transactional.Client +{ + /// <summary> + /// A global configuration object for transactional email clients + /// </summary> + public class TransactionalEmailConfig + { + /// <summary> + /// The server transaction endpoint location + /// </summary> + public Uri ServiceLocation { get; private set; } + + /// <summary> + /// An email id template/translation table for email template-ids + /// </summary> + public IReadOnlyDictionary<string, string> TemplateIdLookup { get; private set; } + + /// <summary> + /// Adds the mail service location to the current instance + /// </summary> + /// <param name="serviceLocation">The address of the remote server transaction endpoint</param> + /// <returns>A referrence to the current object (fluent api)</returns> + /// <exception cref="ArgumentNullException"></exception> + public TransactionalEmailConfig WithUrl(Uri serviceLocation) + { + ServiceLocation = serviceLocation ?? throw new ArgumentNullException(nameof(serviceLocation)); + return this; + } + /// <summary> + /// Sets the template lookup table for the current instance + /// </summary> + /// <param name="templates">The template-id lookup table to referrence</param> + /// <returns>A referrence to the current object (fluent api)</returns> + /// <exception cref="ArgumentNullException"></exception> + public TransactionalEmailConfig WithTemplates(IReadOnlyDictionary<string, string> templates) + { + TemplateIdLookup = templates ?? throw new ArgumentNullException(nameof(templates)); + return this; + } + + /// <summary> + /// Gets a new <see cref="EmailTransactionRequest"/> from the specifed + /// template name. + /// </summary> + /// <param name="templateName"></param> + /// <returns></returns> + /// <exception cref="KeyNotFoundException"></exception> + /// <exception cref="ArgumentNullException"></exception> + public EmailTransactionRequest GetTemplateRequest(string templateName) + { + //get the template from its template name + string templateId = TemplateIdLookup[templateName]; + return new EmailTransactionRequest(templateId) + { + Endpoint = this.ServiceLocation + }; + } + } +} diff --git a/Emails.Transactional.Client/ValidationErrorMessage.cs b/Emails.Transactional.Client/ValidationErrorMessage.cs new file mode 100644 index 0000000..5e7d728 --- /dev/null +++ b/Emails.Transactional.Client/ValidationErrorMessage.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace Emails.Transactional.Client +{ + /// <summary> + /// A json serializable server validaion error + /// </summary> + public class ValidationErrorMessage + { + /// <summary> + /// The name of the propery that was invalid + /// </summary> + [JsonPropertyName("property")] + public string PropertyName { get; set; } + /// <summary> + /// The message that + /// </summary> + [JsonPropertyName("message")] + public string ErrorMessage { get; set; } + } +} |