Skip to content

Commit

Permalink
Add CancellationToken to async mapper
Browse files Browse the repository at this point in the history
  • Loading branch information
RehanSaeed committed Jul 20, 2020
1 parent efe8047 commit ad7462d
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 57 deletions.
116 changes: 77 additions & 39 deletions Source/Boxed.Mapping/AsyncMapperExtensions.cs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Source/Boxed.Mapping/Boxed.Mapping.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<PropertyGroup Label="Build">
<TargetFrameworks>netstandard1.3;netstandard2.0;netstandard2.1</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup Label="Package">
Expand Down
6 changes: 4 additions & 2 deletions Source/Boxed.Mapping/IAsyncMapper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace Boxed.Mapping
{
using System.Threading;
using System.Threading.Tasks;

/// <summary>
Expand All @@ -14,7 +15,8 @@ public interface IAsyncMapper<in TSource, in TDestination>
/// </summary>
/// <param name="source">The source object to map from.</param>
/// <param name="destination">The destination object to map to.</param>
/// <returns>A task representing the operation.</returns>
Task MapAsync(TSource source, TDestination destination);
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task MapAsync(TSource source, TDestination destination, CancellationToken cancellationToken);
}
}
6 changes: 5 additions & 1 deletion Tests/Boxed.Mapping.Test/AsyncMapper.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
namespace Boxed.Mapping.Test
{
using System;
using System.Threading;
using System.Threading.Tasks;

public class AsyncMapper : IAsyncMapper<MapFrom, MapTo>
{
public Task MapAsync(MapFrom from, MapTo to)
public CancellationToken CancellationToken { get; private set; }

public Task MapAsync(MapFrom from, MapTo to, CancellationToken cancellationToken)
{
if (from is null)
{
Expand All @@ -17,6 +20,7 @@ public Task MapAsync(MapFrom from, MapTo to)
throw new ArgumentNullException(nameof(to));
}

this.CancellationToken = cancellationToken;
to.Property = from.Property;
return Task.FromResult<object>(null);
}
Expand Down
59 changes: 44 additions & 15 deletions Tests/Boxed.Mapping.Test/AsyncMapperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,34 @@ namespace Boxed.Mapping.Test
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

public class AsyncMapperTest
public class AsyncMapperTest : Disposable
{
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

[Fact]
public Task MapAsync_Null_ThrowsArgumentNullExceptionAsync()
{
var mapper = new AsyncMapper();

return Assert.ThrowsAsync<ArgumentNullException>("source", () => mapper.MapAsync(null));
return Assert.ThrowsAsync<ArgumentNullException>(
"source",
() => mapper.MapAsync(null, this.cancellationTokenSource.Token));
}

[Fact]
public async Task MapAsync_ToNewObject_MappedAsync()
{
var mapper = new AsyncMapper();

var to = await mapper.MapAsync(new MapFrom() { Property = 1 }).ConfigureAwait(false);
var to = await mapper
.MapAsync(new MapFrom() { Property = 1 }, this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.Equal(this.cancellationTokenSource.Token, mapper.CancellationToken);
Assert.Equal(1, to.Property);
}

Expand All @@ -32,7 +40,9 @@ public async Task MapArrayAsync_Empty_MappedAsync()
{
var mapper = new AsyncMapper();

var to = await mapper.MapArrayAsync(Array.Empty<MapFrom>()).ConfigureAwait(false);
var to = await mapper
.MapArrayAsync(Array.Empty<MapFrom>(), this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.IsType<MapTo[]>(to);
Assert.Empty(to);
Expand All @@ -49,9 +59,11 @@ public async Task MapArrayAsync_ToNewObject_MappedAsync()
{
new MapFrom() { Property = 1 },
new MapFrom() { Property = 2 },
})
},
this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.Equal(this.cancellationTokenSource.Token, mapper.CancellationToken);
Assert.IsType<MapTo[]>(to);
Assert.Equal(2, to.Length);
Assert.Equal(1, to[0].Property);
Expand All @@ -63,7 +75,9 @@ public async Task MapTypedCollectionAsync_Empty_MappedAsync()
{
var mapper = new AsyncMapper();

var to = await mapper.MapCollectionAsync(Array.Empty<MapFrom>(), new List<MapTo>()).ConfigureAwait(false);
var to = await mapper
.MapCollectionAsync(Array.Empty<MapFrom>(), new List<MapTo>(), this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.IsType<List<MapTo>>(to);
Assert.Empty(to);
Expand All @@ -81,9 +95,11 @@ public async Task MapTypedCollectionAsync_ToNewObject_MappedAsync()
new MapFrom() { Property = 1 },
new MapFrom() { Property = 2 },
},
new List<MapTo>())
new List<MapTo>(),
this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.Equal(this.cancellationTokenSource.Token, mapper.CancellationToken);
Assert.IsType<List<MapTo>>(to);
Assert.Equal(2, to.Count);
Assert.Equal(1, to[0].Property);
Expand All @@ -95,7 +111,9 @@ public async Task MapCollectionAsync_Empty_MappedAsync()
{
var mapper = new AsyncMapper();

var to = await mapper.MapCollectionAsync(Array.Empty<MapFrom>()).ConfigureAwait(false);
var to = await mapper
.MapCollectionAsync(Array.Empty<MapFrom>(), this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.IsType<Collection<MapTo>>(to);
Assert.Empty(to);
Expand All @@ -112,9 +130,11 @@ public async Task MapCollectionAsync_ToNewObject_MappedAsync()
{
new MapFrom() { Property = 1 },
new MapFrom() { Property = 2 },
})
},
this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.Equal(this.cancellationTokenSource.Token, mapper.CancellationToken);
Assert.IsType<Collection<MapTo>>(to);
Assert.Equal(2, to.Count);
Assert.Equal(1, to[0].Property);
Expand All @@ -126,7 +146,9 @@ public async Task MapListAsync_Empty_MappedAsync()
{
var mapper = new AsyncMapper();

var to = await mapper.MapListAsync(Array.Empty<MapFrom>()).ConfigureAwait(false);
var to = await mapper
.MapListAsync(Array.Empty<MapFrom>(), this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.IsType<List<MapTo>>(to);
Assert.Empty(to);
Expand All @@ -143,9 +165,11 @@ public async Task MapListAsync_ToNewObject_MappedAsync()
{
new MapFrom() { Property = 1 },
new MapFrom() { Property = 2 },
})
},
this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.Equal(this.cancellationTokenSource.Token, mapper.CancellationToken);
Assert.IsType<List<MapTo>>(to);
Assert.Equal(2, to.Count);
Assert.Equal(1, to[0].Property);
Expand All @@ -158,7 +182,7 @@ public async Task MapObservableCollectionAsync_Empty_MappedAsync()
var mapper = new AsyncMapper();

var to = await mapper
.MapObservableCollectionAsync(Array.Empty<MapFrom>())
.MapObservableCollectionAsync(Array.Empty<MapFrom>(), this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.IsType<ObservableCollection<MapTo>>(to);
Expand All @@ -176,17 +200,19 @@ public async Task MapObservableCollectionAsync_ToNewObject_MappedAsync()
{
new MapFrom() { Property = 1 },
new MapFrom() { Property = 2 },
})
},
this.cancellationTokenSource.Token)
.ConfigureAwait(false);

Assert.Equal(this.cancellationTokenSource.Token, mapper.CancellationToken);
Assert.IsType<ObservableCollection<MapTo>>(to);
Assert.Equal(2, to.Count);
Assert.Equal(1, to[0].Property);
Assert.Equal(2, to[1].Property);
}

[Fact]
public async Task MapAsyncEnumerable_ToNewObject_MappedAsync()
public async Task MapEnumerableAsync_ToNewObject_MappedAsync()
{
var mapper = new AsyncMapper();
var source = new TestAsyncEnumerable<MapFrom>(
Expand All @@ -196,13 +222,16 @@ public async Task MapAsyncEnumerable_ToNewObject_MappedAsync()
new MapFrom() { Property = 2 },
});

var to = mapper.MapEnumerableAsync(source);
var to = mapper.MapEnumerableAsync(source, this.cancellationTokenSource.Token);

var list = await to.ToListAsync().ConfigureAwait(false);
Assert.Equal(this.cancellationTokenSource.Token, mapper.CancellationToken);
Assert.IsType<List<MapTo>>(list);
Assert.Equal(2, list.Count);
Assert.Equal(1, list[0].Property);
Assert.Equal(2, list[1].Property);
}

protected override void DisposeManaged() => this.cancellationTokenSource.Dispose();
}
}
91 changes: 91 additions & 0 deletions Tests/Boxed.Mapping.Test/Disposable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
namespace Boxed.Mapping.Test
{
using System;

/// <summary>
/// Base class for members implementing <see cref="IDisposable"/>.
/// </summary>
public abstract class Disposable : IDisposable
{
/// <summary>
/// Finalizes an instance of the <see cref="Disposable"/> class. Releases unmanaged
/// resources and performs other clean-up operations before the <see cref="Disposable"/>
/// is reclaimed by garbage collection. Will run only if the
/// Dispose method does not get called.
/// </summary>
~Disposable()
{
this.Dispose(false);
}

/// <summary>
/// Gets a value indicating whether this <see cref="Disposable"/> is disposed.
/// </summary>
/// <value><c>true</c> if disposed; otherwise, <c>false</c>.</value>
public bool IsDisposed { get; private set; }

/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
// Dispose all managed and unmanaged resources.
this.Dispose(true);

// Take this object off the finalization queue and prevent finalization code for this
// object from executing a second time.
GC.SuppressFinalize(this);
}

/// <summary>
/// Disposes the managed resources implementing <see cref="IDisposable"/>.
/// </summary>
protected virtual void DisposeManaged()
{
}

/// <summary>
/// Disposes the unmanaged resources implementing <see cref="IDisposable"/>.
/// </summary>
protected virtual void DisposeUnmanaged()
{
}

/// <summary>
/// Throws a <see cref="ObjectDisposedException"/> if this instance is disposed.
/// </summary>
protected void ThrowIfDisposed()
{
if (this.IsDisposed)
{
throw new ObjectDisposedException(this.GetType().Name);
}
}

/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources;
/// <c>false</c> to release only unmanaged resources, called from the finalizer only.</param>
/// <remarks>We suppress CA1063 which requires that this method be protected virtual because we want to hide
/// the internal implementation.</remarks>
#pragma warning disable CA1063 // Implement IDisposable Correctly
private void Dispose(bool disposing)
#pragma warning restore CA1063 // Implement IDisposable Correctly
{
// Check to see if Dispose has already been called.
if (!this.IsDisposed)
{
// If disposing managed and unmanaged resources.
if (disposing)
{
this.DisposeManaged();
}

this.DisposeUnmanaged();

this.IsDisposed = true;
}
}
}
}

0 comments on commit ad7462d

Please sign in to comment.