Skip to content

Commit 92b8b8f

Browse files
authored
feat: upsert external role assignments command (#410)
1 parent 59da01d commit 92b8b8f

23 files changed

+1017
-131
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Altinn.Authorization.ServiceDefaults.MassTransit;
2+
using Altinn.Register.Contracts.Parties;
3+
using MassTransit;
4+
5+
namespace Altinn.Register.Contracts.ExternalRoles;
6+
7+
/// <summary>
8+
/// An event that is published when an external role assignment is added.
9+
/// </summary>
10+
[MessageUrn("event:altinn-register:external-role-assignment-added")]
11+
public sealed record ExternalRoleAssignmentAddedEvent
12+
: EventBase
13+
{
14+
/// <summary>
15+
/// Gets the role that was assigned.
16+
/// </summary>
17+
public required ExternalRoleReference Role { get; init; }
18+
19+
/// <summary>
20+
/// Gets the party that the role was assigned from.
21+
/// </summary>
22+
public required PartyReference From { get; init; }
23+
24+
/// <summary>
25+
/// Gets the party that the role was assigned to.
26+
/// </summary>
27+
public required PartyReference To { get; init; }
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Altinn.Authorization.ServiceDefaults.MassTransit;
2+
using Altinn.Register.Contracts.Parties;
3+
using MassTransit;
4+
5+
namespace Altinn.Register.Contracts.ExternalRoles;
6+
7+
/// <summary>
8+
/// An event that is published when an external role assignment is removed.
9+
/// </summary>
10+
[MessageUrn("event:altinn-register:external-role-assignment-removed")]
11+
public sealed record ExternalRoleAssignmentRemovedEvent
12+
: EventBase
13+
{
14+
/// <summary>
15+
/// Gets the role that was assigned.
16+
/// </summary>
17+
public required ExternalRoleReference Role { get; init; }
18+
19+
/// <summary>
20+
/// Gets the party that the role was assigned from.
21+
/// </summary>
22+
public required PartyReference From { get; init; }
23+
24+
/// <summary>
25+
/// Gets the party that the role was assigned to.
26+
/// </summary>
27+
public required PartyReference To { get; init; }
28+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
namespace Altinn.Register.Contracts.ExternalRoles;
4+
5+
/// <summary>
6+
/// Represents a reference to an external role.
7+
/// </summary>
8+
public sealed record ExternalRoleReference
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="ExternalRoleReference"/> class.
12+
/// </summary>
13+
/// <param name="source">The source of the external role.</param>
14+
/// <param name="identifier">The identifier of the external role.</param>
15+
[SetsRequiredMembers]
16+
public ExternalRoleReference(ExternalRoleSource source, string identifier)
17+
{
18+
Source = source;
19+
Identifier = identifier;
20+
}
21+
22+
/// <summary>
23+
/// Gets the source of the external role.
24+
/// </summary>
25+
public required ExternalRoleSource Source { get; init; }
26+
27+
/// <summary>
28+
/// Gets the identifier of the external role.
29+
/// </summary>
30+
public required string Identifier { get; init; }
31+
}

src/Altinn.Register/src/Altinn.Register.Contracts/Parties/PartyUpdatedEvent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Altinn.Authorization.ServiceDefaults.MassTransit;
1+
using Altinn.Authorization.ServiceDefaults.MassTransit;
22
using MassTransit;
33

44
namespace Altinn.Register.Contracts.Parties;

src/Altinn.Register/src/Altinn.Register.Core/Parties/IPartyExternalRolePersistence.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Diagnostics.CodeAnalysis;
22
using Altinn.Register.Contracts.ExternalRoles;
33
using Altinn.Register.Core.Parties.Records;
4+
using Altinn.Register.Core.Utils;
45

56
namespace Altinn.Register.Core.Parties;
67

@@ -48,7 +49,7 @@ public IAsyncEnumerable<PartyExternalRoleAssignmentRecord> GetExternalRoleAssign
4849
/// <param name="assignments">The new role-assignments.</param>
4950
/// <param name="cancellationToken">A <see cref="CancellationToken"/>.</param>
5051
/// <returns></returns>
51-
public IAsyncEnumerable<ExternalRoleAssignmentEvent> UpsertExternalRolesFromPartyBySource(
52+
public IAsyncSideEffectEnumerable<ExternalRoleAssignmentEvent> UpsertExternalRolesFromPartyBySource(
5253
Guid commandId,
5354
Guid partyUuid,
5455
ExternalRoleSource roleSource,

src/Altinn.Register/src/Altinn.Register.Core/Utils/AsyncEnumerableExtensions.cs

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#nullable enable
22

3+
using System.Runtime.CompilerServices;
34
using CommunityToolkit.Diagnostics;
45

56
namespace Altinn.Register.Core.Utils;
@@ -28,7 +29,26 @@ public static IAsyncEnumerable<T> WrapExceptions<T>(
2829
return new WrapExceptionsAsyncEnumerable<T>(source, wrap, cancellationToken);
2930
}
3031

31-
private sealed class WrapExceptionsAsyncEnumerable<T>
32+
/// <summary>
33+
/// Wraps exceptions thrown by the source enumerable in a new exception.
34+
/// </summary>
35+
/// <typeparam name="T">The item type.</typeparam>
36+
/// <param name="source">The source enumerable.</param>
37+
/// <param name="wrap">The exception wrapper.</param>
38+
/// <param name="cancellationToken">Cancellation token (used to filter out <see cref="OperationCanceledException"/>s).</param>
39+
/// <returns>The source enumerable, but with any exceptions optionally wrapped by <paramref name="wrap"/>.</returns>
40+
public static IAsyncSideEffectEnumerable<T> WrapExceptions<T>(
41+
this IAsyncSideEffectEnumerable<T> source,
42+
Func<Exception, Exception?> wrap,
43+
CancellationToken cancellationToken)
44+
{
45+
Guard.IsNotNull(source);
46+
Guard.IsNotNull(wrap);
47+
48+
return new WrapExceptionsAsyncSideEffectEnumerable<T>(source, wrap, cancellationToken);
49+
}
50+
51+
private class WrapExceptionsAsyncEnumerable<T>
3252
: IAsyncEnumerable<T>
3353
{
3454
private readonly IAsyncEnumerable<T> _source;
@@ -45,19 +65,26 @@ public WrapExceptionsAsyncEnumerable(
4565
_cancellationToken = cancellationToken;
4666
}
4767

68+
protected Exception? WrapException(Exception source)
69+
=> _wrap(source);
70+
71+
protected bool ShouldRethrowAsIs(Exception exception)
72+
=> exception is OperationCanceledException ex && ex.CancellationToken == _cancellationToken;
73+
4874
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
4975
{
5076
try
5177
{
5278
return new Enumerator(_source.GetAsyncEnumerator(cancellationToken), _wrap, _cancellationToken);
5379
}
54-
catch (OperationCanceledException ex) when (ex.CancellationToken == _cancellationToken)
55-
{
56-
throw;
57-
}
5880
catch (Exception ex)
5981
{
60-
if (_wrap(ex) is Exception wrapped)
82+
if (ShouldRethrowAsIs(ex))
83+
{
84+
throw;
85+
}
86+
87+
if (WrapException(ex) is Exception wrapped)
6188
{
6289
throw wrapped;
6390
}
@@ -83,6 +110,12 @@ public Enumerator(
83110
_cancellationToken = cancellationToken;
84111
}
85112

113+
private Exception? WrapException(Exception source)
114+
=> _wrap(source);
115+
116+
private bool ShouldRethrowAsIs(Exception exception)
117+
=> exception is OperationCanceledException ex && ex.CancellationToken == _cancellationToken;
118+
86119
public T Current => _source.Current;
87120

88121
public async ValueTask DisposeAsync()
@@ -91,13 +124,14 @@ public async ValueTask DisposeAsync()
91124
{
92125
await _source.DisposeAsync();
93126
}
94-
catch (OperationCanceledException ex) when (ex.CancellationToken == _cancellationToken)
95-
{
96-
throw;
97-
}
98127
catch (Exception ex)
99128
{
100-
if (_wrap(ex) is Exception wrapped)
129+
if (ShouldRethrowAsIs(ex))
130+
{
131+
throw;
132+
}
133+
134+
if (WrapException(ex) is Exception wrapped)
101135
{
102136
throw wrapped;
103137
}
@@ -112,13 +146,14 @@ public async ValueTask<bool> MoveNextAsync()
112146
{
113147
return await _source.MoveNextAsync();
114148
}
115-
catch (OperationCanceledException ex) when (ex.CancellationToken == _cancellationToken)
116-
{
117-
throw;
118-
}
119149
catch (Exception ex)
120150
{
121-
if (_wrap(ex) is Exception wrapped)
151+
if (ShouldRethrowAsIs(ex))
152+
{
153+
throw;
154+
}
155+
156+
if (WrapException(ex) is Exception wrapped)
122157
{
123158
throw wrapped;
124159
}
@@ -128,4 +163,45 @@ public async ValueTask<bool> MoveNextAsync()
128163
}
129164
}
130165
}
166+
167+
private sealed class WrapExceptionsAsyncSideEffectEnumerable<T>
168+
: WrapExceptionsAsyncEnumerable<T>
169+
, IAsyncSideEffectEnumerable<T>
170+
{
171+
private readonly IAsyncSideEffectEnumerable<T> _source;
172+
173+
public WrapExceptionsAsyncSideEffectEnumerable(
174+
IAsyncSideEffectEnumerable<T> source,
175+
Func<Exception, Exception?> wrap,
176+
CancellationToken cancellationToken)
177+
: base(source, wrap, cancellationToken)
178+
{
179+
_source = source;
180+
}
181+
182+
public TaskAwaiter GetAwaiter()
183+
=> Run().GetAwaiter();
184+
185+
private async Task Run()
186+
{
187+
try
188+
{
189+
await _source;
190+
}
191+
catch (Exception ex)
192+
{
193+
if (ShouldRethrowAsIs(ex))
194+
{
195+
throw;
196+
}
197+
198+
if (WrapException(ex) is Exception wrapped)
199+
{
200+
throw wrapped;
201+
}
202+
203+
throw;
204+
}
205+
}
206+
}
131207
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#nullable enable
2+
3+
using System.Runtime.CompilerServices;
4+
5+
namespace Altinn.Register.Core.Utils;
6+
7+
/// <summary>
8+
/// Represents an async enumerable that can be executed just for its side effects.
9+
/// </summary>
10+
/// <typeparam name="T">The item type.</typeparam>
11+
public interface IAsyncSideEffectEnumerable<out T>
12+
: IAsyncEnumerable<T>
13+
{
14+
/// <summary>Gets an awaiter used to await this <see cref="IAsyncSideEffectEnumerable{T}"/>.</summary>
15+
/// <returns>An awaiter instance.</returns>
16+
TaskAwaiter GetAwaiter();
17+
}

src/Altinn.Register/src/Altinn.Register.Persistence/Altinn.Register.Persistence.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,8 @@
2323
<EmbeddedResource Include="TestData/**/*.sql" />
2424
</ItemGroup>
2525

26+
<ItemGroup>
27+
<InternalsVisibleTo Include="Altinn.Register.TestUtils"/>
28+
</ItemGroup>
29+
2630
</Project>

0 commit comments

Comments
 (0)