Skip to content

Commit

Permalink
Sms client implemented #GCPActive (#9)
Browse files Browse the repository at this point in the history
* Sms client implemented

* added stylecop to integration + core projects

* Delete test/Altinn.Notifications.Sms.IntegrationTests/Altinn - Backup.Notifications.Sms.IntegrationTests.csproj

* Update src/Altinn.Notifications.Sms.Core/Integrations/Interfaces/ISmsClient.cs

* aded wrapper for sms gateway client

* added more tests

* merged main

* Fixed namespaces

* fixed stylecop + global supression
  • Loading branch information
acn-sbuad authored Jan 22, 2024
1 parent 994ed2c commit 9ec7054
Show file tree
Hide file tree
Showing 25 changed files with 494 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,14 @@
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
</ItemGroup>

<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<AdditionalFiles Include="..\..\stylecop.json">
<Link>stylecop.json</Link>
</AdditionalFiles>
</ItemGroup>
</Project>
7 changes: 0 additions & 7 deletions src/Altinn.Notifications.Sms.Core/Class1.cs

This file was deleted.

18 changes: 18 additions & 0 deletions src/Altinn.Notifications.Sms.Core/Dependencies/ISmsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Altinn.Notifications.Sms.Core.Sending;
using Altinn.Notifications.Sms.Core.Shared;

namespace Altinn.Notifications.Sms.Core.Dependencies
{
/// <summary>
/// This interface describes the public interface of a client able to send sms messages through an sms service.
/// </summary>
public interface ISmsClient
{
/// <summary>
/// Method for requesting the sending on an sms message.
/// </summary>
/// <param name="sms">The sms to be sent</param>
/// <returns>An id for tracing the sucess of the task or an <see cref="SmsClientErrorResponse"/> it the task fails</returns>
public Task<Result<string, SmsClientErrorResponse>> SendAsync(Sending.Sms sms);
}
}
26 changes: 26 additions & 0 deletions src/Altinn.Notifications.Sms.Core/Sending/Sms.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Altinn.Notifications.Sms.Core.Sending
{
/// <summary>
/// Class representing an sms message
/// </summary>
public class Sms
{
/// <summary>
/// Gets or sets the contents of the sms message
/// </summary>
public string Message { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the recipient of the sms message
/// </summary>
public string Recipient { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the sender of the sms message
/// </summary>
/// <remarks>
/// Can be a literal string or a phone number
/// </remarks>
public string Sender { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Altinn.Notifications.Sms.Core.Status;

namespace Altinn.Notifications.Sms.Core.Sending
{
/// <summary>
/// Class representing an error response from an sms client service
/// </summary>
public class SmsClientErrorResponse
{
/// <summary>
/// Result for the send operation
/// </summary>
public SmsSendResult SendResult { get; set; }

/// <summary>
/// The error message from the sms client service
/// </summary>
public string? ErrorMessage { get; set; }
}
}
61 changes: 61 additions & 0 deletions src/Altinn.Notifications.Sms.Core/Shared/Result.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace Altinn.Notifications.Sms.Core.Shared;

/// <summary>
/// A simple implementation of the Result class to handle success XOR failure as separate return
/// values in a type safe way.
/// </summary>
/// <typeparam name="TValue">The type to be assigned to indicate success.</typeparam>
/// <typeparam name="TError">The type to be assigned to indicate failure.</typeparam>
public readonly struct Result<TValue, TError>
{
private readonly TValue? _value;
private readonly TError? _error;

private Result(TValue value)
{
IsError = false;
_value = value;
_error = default;
}

private Result(TError error)
{
IsError = true;
_value = default;
_error = error;
}

/// <summary>
/// Gets a value indicating whether the Result contains an error value.
/// </summary>
public bool IsError { get; }

/// <summary>
/// Gets a value indicating whether the Result contains a success value.
/// </summary>
public bool IsSuccess => !IsError;

/// <summary>
/// Implicit operator used when creating an instance of Result when assigning a success value.
/// </summary>
/// <param name="value">An object of the type indicating success.</param>
public static implicit operator Result<TValue, TError>(TValue value) => new(value);

/// <summary>
/// Implicit operator used when creating an instance of Result when assigning an error value.
/// </summary>
/// <param name="error">An object of the type indicating failure.</param>
public static implicit operator Result<TValue, TError>(TError error) => new(error);

/// <summary>
/// This method will call either the success OR the failure function based on it's error state.
/// </summary>
/// <typeparam name="TResult">The type to be returned by the given functions.</typeparam>
/// <param name="success">The function to call if Result holds a success value.</param>
/// <param name="failure">The function to call if Result holds an error value.</param>
/// <returns>An instance of the defined type.</returns>
public TResult Match<TResult>(
Func<TValue, TResult> success,
Func<TError, TResult> failure) =>
!IsError ? success(_value!) : failure(_error!);
}
13 changes: 13 additions & 0 deletions src/Altinn.Notifications.Sms.Core/Status/SmsSendResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Altinn.Notifications.Sms.Core.Status
{
/// <summary>
/// Enum describing sms send result types
/// </summary>
public enum SmsSendResult
{
Sending,
Accepted,
Failed,
Failed_InvalidReceiver
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,24 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="LinkMobilityNE.PSWin.Client" Version="1.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Altinn.Notifications.Sms.Core\Altinn.Notifications.Sms.Core.csproj" />
</ItemGroup>


<ItemGroup Condition="'$(Configuration)'=='Debug'">
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.507">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<AdditionalFiles Include="..\..\stylecop.json">
<Link>stylecop.json</Link>
</AdditionalFiles>
</ItemGroup>
</Project>
7 changes: 0 additions & 7 deletions src/Altinn.Notifications.Sms.Integrations/Class1.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Microsoft.Extensions.Configuration;
using Altinn.Notifications.Sms.Core.Dependencies;
using Altinn.Notifications.Sms.Integrations.LinkMobility;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Altinn.Notifications.Sms.Integrations.Configuration;
Expand All @@ -16,6 +19,16 @@ public static class ServiceCollectionExtensions
/// <returns>The given service collection.</returns>
public static IServiceCollection AddIntegrationServices(this IServiceCollection services, IConfiguration config)
{
SmsGatewayConfiguration smsGatewaySettings = config!.GetSection(nameof(SmsGatewayConfiguration)).Get<SmsGatewayConfiguration>()!;

if (smsGatewaySettings == null)
{
throw new ArgumentNullException(nameof(config), "Required SmsGatewayConfiguration settings is missing from application configuration.");
}

services.AddSingleton<ISmsClient, SmsClient>()
.AddSingleton<IAltinnGatewayClient, AltinnGatewayClient>()
.AddSingleton(smsGatewaySettings);
return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System.Diagnostics.CodeAnalysis;

using LinkMobility.PSWin.Client;
using LinkMobility.PSWin.Client.Model;
using LinkMobility.PSWin.Client.Transports;

using LinkMobilityModel = LinkMobility.PSWin.Client.Model;

namespace Altinn.Notifications.Sms.Integrations.LinkMobility;

/// <summary>
/// Wrapper class for the LinkMobility SMS Gateway client to support DI
/// </summary>
[ExcludeFromCodeCoverage]
public class AltinnGatewayClient : IAltinnGatewayClient
{
private readonly GatewayClient _client;

/// <summary>
/// Initializes a new instance of the <see cref="AltinnGatewayClient"/> class.
/// </summary>
public AltinnGatewayClient(SmsGatewayConfiguration gatewayConfig)
{
_client = new(new XmlTransport(gatewayConfig.Username, gatewayConfig.Password, new Uri(gatewayConfig.Endpoint)));
}

/// <inheritdoc/>
public async Task<MessageResult> SendAsync(LinkMobilityModel.Sms message)
{
return await _client.SendAsync(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using LinkMobility.PSWin.Client.Model;

using LinkMobilityModel = LinkMobility.PSWin.Client.Model;

namespace Altinn.Notifications.Sms.Integrations.LinkMobility
{
/// <summary>
/// Interface for the gateway client
/// </summary>
public interface IAltinnGatewayClient
{
/// <summary>
/// Send sms async
/// </summary>
public Task<MessageResult> SendAsync(LinkMobilityModel.Sms message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Altinn.Notifications.Sms.Core.Dependencies;
using Altinn.Notifications.Sms.Core.Sending;
using Altinn.Notifications.Sms.Core.Shared;
using Altinn.Notifications.Sms.Core.Status;
using LinkMobility.PSWin.Client.Model;

using LinkMobilityModel = global::LinkMobility.PSWin.Client.Model;

namespace Altinn.Notifications.Sms.Integrations.LinkMobility
{
/// <summary>
/// Represents an implementation of <see cref="ISmsClient"/> that will use LinkMobility's
/// SMS gateway to send text messages.
/// </summary>
public class SmsClient : ISmsClient
{
private readonly IAltinnGatewayClient _client;

/// <summary>
/// Initializes a new instance of the <see cref="SmsClient"/> class.
/// </summary>
/// <param name="gatewayConfig">The configuration for the sms gateway</param>
public SmsClient(IAltinnGatewayClient client)
{
_client = client;
}

/// <inheritdoc />
public async Task<Result<string, SmsClientErrorResponse>> SendAsync(Core.Sending.Sms sms)
{
MessageResult result = await _client.SendAsync(new LinkMobilityModel.Sms(sms.Recipient, sms.Message, sms.Sender));

if (result.IsStatusOk)
{
return result.GatewayReference;
}

if (result.StatusText.StartsWith("Invalid RCV"))
{
return new SmsClientErrorResponse { SendResult = SmsSendResult.Failed_InvalidReceiver, ErrorMessage = result.StatusText };
}

return new SmsClientErrorResponse { SendResult = SmsSendResult.Failed };
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Altinn.Notifications.Sms.Integrations.LinkMobility
{
/// <summary>
/// Configuration for the LinkMobility SMS gateway
/// </summary>
public class SmsGatewayConfiguration
{
/// <summary>
/// Username to use for authentication towards the SMS gateway
/// </summary>
public string Username { get; set; } = string.Empty;

/// <summary>
/// Password to use for authentication towards the SMS gateway
/// </summary>
public string Password { get; set; } = string.Empty;

/// <summary>
/// Url to the SMS gateway endpoint
/// </summary>
public string Endpoint { get; set; } = string.Empty;
}
}
25 changes: 0 additions & 25 deletions src/Altinn.Notifications.Sms/Controllers/SetupController.cs

This file was deleted.

12 changes: 10 additions & 2 deletions src/Altinn.Notifications.Sms/Health/HealthTelemetryFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ namespace Altinn.Notifications.Sms.Health
/// Initializes a new instance of the <see cref="HealthTelemetryFilter"/> class.
/// </remarks>
[ExcludeFromCodeCoverage]
public class HealthTelemetryFilter(ITelemetryProcessor next) : ITelemetryProcessor
public class HealthTelemetryFilter : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; } = next;
private ITelemetryProcessor Next { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="HealthTelemetryFilter"/> class.
/// </summary>
public HealthTelemetryFilter(ITelemetryProcessor next)
{
Next = next;
}

/// <inheritdoc/>
public void Process(ITelemetry item)
Expand Down
Loading

0 comments on commit 9ec7054

Please sign in to comment.