Skip to content

Commit 076ce93

Browse files
committed
feat(Runner): Implement the AsyncApiCallExecutor
Signed-off-by: Charles d'Avernas <charles.davernas@neuroglia.io>
1 parent 86535bc commit 076ce93

File tree

14 files changed

+63
-46
lines changed

14 files changed

+63
-46
lines changed

src/api/Synapse.Api.Client.Http/Synapse.Api.Client.Http.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
<ItemGroup>
4545
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
46-
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.2" />
46+
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha6.2" />
4747
<PackageReference Include="System.Reactive" Version="6.0.1" />
4848
</ItemGroup>
4949

src/api/Synapse.Api.Http/Synapse.Api.Http.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
<ItemGroup>
4646
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.18.1" />
4747
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.18.1" />
48-
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.1.0" />
48+
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.2.0" />
4949
</ItemGroup>
5050

5151
<ItemGroup>

src/api/Synapse.Api.Server/Synapse.Api.Server.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="9.0.0" />
3636
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="9.0.0" />
3737
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
38-
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.1.0" />
38+
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.2.0" />
3939
</ItemGroup>
4040

4141
<ItemGroup>

src/cli/Synapse.Cli/Synapse.Cli.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
3434
<PackageReference Include="moment.net" Version="1.3.4" />
3535
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
36-
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.2" />
36+
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha6.2" />
3737
<PackageReference Include="Spectre.Console" Version="0.49.1" />
3838
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
3939
</ItemGroup>

src/core/Synapse.Core.Infrastructure/Synapse.Core.Infrastructure.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<PackageReference Include="Neuroglia.Mediation" Version="4.18.1" />
5252
<PackageReference Include="Neuroglia.Plugins" Version="4.18.1" />
5353
<PackageReference Include="Neuroglia.Serialization.Xml" Version="4.18.1" />
54-
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.2" />
54+
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha6.2" />
5555
</ItemGroup>
5656

5757
<ItemGroup>

src/core/Synapse.Core/Synapse.Core.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
<PackageReference Include="Neuroglia.Data.Infrastructure.ResourceOriented" Version="4.18.1" />
7171
<PackageReference Include="Neuroglia.Eventing.CloudEvents" Version="4.18.1" />
7272
<PackageReference Include="Semver" Version="3.0.0" />
73-
<PackageReference Include="ServerlessWorkflow.Sdk" Version="1.0.0-alpha5.2" />
73+
<PackageReference Include="ServerlessWorkflow.Sdk" Version="1.0.0-alpha6.2" />
7474
</ItemGroup>
7575

7676
</Project>

src/correlator/Synapse.Correlator/Synapse.Correlator.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@
4242
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.18.1" />
4343
<PackageReference Include="Neuroglia.Eventing.CloudEvents.Infrastructure" Version="4.18.1" />
4444
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.18.1" />
45-
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.1.0" />
46-
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.0.0" />
45+
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.2.0" />
46+
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.2.0" />
4747
</ItemGroup>
4848

4949
<ItemGroup>

src/dashboard/Synapse.Dashboard/Services/WorkflowGraphBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,15 +226,15 @@ protected INodeViewModel BuildTaskNode(TaskNodeRenderingContext context)
226226
protected virtual NodeViewModel BuildCallTaskNode(TaskNodeRenderingContext<CallTaskDefinition> context)
227227
{
228228
ArgumentNullException.ThrowIfNull(context);
229-
var content = string.Empty;
229+
string content; ;
230230
string callType;
231231
switch (context.TaskDefinition.Call.ToLower())
232232
{
233233
case "asyncapi":
234234
{
235235
var definition = (AsyncApiCallDefinition)this.JsonSerializer.Convert(context.TaskDefinition.With, typeof(AsyncApiCallDefinition))!;
236236
callType = context.TaskDefinition.Call.ToLower();
237-
content = definition.OperationRef;
237+
content = definition.Operation!;
238238
break;
239239
}
240240
case "grpc":

src/dashboard/Synapse.Dashboard/Synapse.Dashboard.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="Blazor.Bootstrap" Version="3.2.0" />
14-
<PackageReference Include="BlazorMonaco" Version="3.2.0" />
14+
<PackageReference Include="BlazorMonaco" Version="3.3.0" />
1515
<PackageReference Include="IdentityModel" Version="7.0.0" />
1616
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
1717
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />

src/runner/Synapse.Runner/Services/Executors/AsyncApiCallExecutor.cs

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -107,23 +107,21 @@ protected override async Task DoInitializeAsync(CancellationToken cancellationTo
107107
var document = await this.AsyncApiDocumentReader.ReadAsync(responseStream, cancellationToken).ConfigureAwait(false);
108108
if (document is not V3AsyncApiDocument v3Document) throw new NotSupportedException("Synapse only supports AsyncAPI v3.0.0 at the moment");
109109
this.Document = v3Document;
110-
var operationId = this.AsyncApi.OperationRef;
110+
if (string.IsNullOrWhiteSpace(this.AsyncApi.Operation)) throw new NullReferenceException("The 'operation' parameter must be set when performing an AsyncAPI v3 call");
111+
var operationId = this.AsyncApi.Operation;
111112
if (operationId.IsRuntimeExpression()) operationId = await this.Task.Workflow.Expressions.EvaluateAsync<string>(operationId, this.Task.Input, this.GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false);
112113
if (string.IsNullOrWhiteSpace(operationId)) throw new NullReferenceException("The operation ref cannot be null or empty");
113-
var operation = this.Document.Operations.FirstOrDefault(o => o.Key == operationId);
114-
if (operation.Value == null) throw new NullReferenceException($"Failed to find an operation with id '{operationId}' in AsyncAPI document at '{uri}'");
114+
this.Operation = this.Document.Operations.FirstOrDefault(o => o.Key == operationId);
115+
if (this.Operation.Value == null) throw new NullReferenceException($"Failed to find an operation with id '{operationId}' in AsyncAPI document at '{uri}'");
115116
if (this.AsyncApi.Authentication != null) this.Authorization = await AuthorizationInfo.CreateAsync(this.AsyncApi.Authentication, this.ServiceProvider, this.Task.Workflow.Definition, cancellationToken).ConfigureAwait(false);
116117
switch (this.Operation.Value.Action)
117118
{
118119
case V3OperationAction.Receive:
119120
await this.BuildMessagePayloadAsync(cancellationToken).ConfigureAwait(false);
120121
await this.BuildMessageHeadersAsync(cancellationToken).ConfigureAwait(false);
121122
break;
122-
case V3OperationAction.Send:
123-
124-
break;
125-
default:
126-
throw new NotSupportedException($"The specified operation action '{this.Operation.Value.Action}' is not supported");
123+
case V3OperationAction.Send: break;
124+
default: throw new NotSupportedException($"The specified operation action '{this.Operation.Value.Action}' is not supported");
127125
}
128126
}
129127

@@ -134,16 +132,16 @@ protected override async Task DoInitializeAsync(CancellationToken cancellationTo
134132
/// <returns>A new awaitable <see cref="Task"/></returns>
135133
protected virtual async Task BuildMessagePayloadAsync(CancellationToken cancellationToken = default)
136134
{
137-
if (this.AsyncApi == null || this.Operation == null) throw new InvalidOperationException("The executor must be initialized before execution");
135+
if (this.AsyncApi == null || this.Operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution");
138136
if (this.Task.Input == null) this.MessagePayload = new { };
139-
if (this.AsyncApi.Payload == null) return;
137+
if (this.AsyncApi.Message?.Payload == null) return;
140138
var arguments = this.GetExpressionEvaluationArguments();
141139
if (this.Authorization != null)
142140
{
143141
arguments ??= new Dictionary<string, object>();
144142
arguments.Add("authorization", this.Authorization);
145143
}
146-
this.MessagePayload = await this.Task.Workflow.Expressions.EvaluateAsync<object>(this.AsyncApi.Payload, this.Task.Input!, arguments, cancellationToken).ConfigureAwait(false);
144+
this.MessagePayload = await this.Task.Workflow.Expressions.EvaluateAsync<object>(this.AsyncApi.Message.Payload, this.Task.Input!, arguments, cancellationToken).ConfigureAwait(false);
147145
}
148146

149147
/// <summary>
@@ -153,30 +151,27 @@ protected virtual async Task BuildMessagePayloadAsync(CancellationToken cancella
153151
/// <returns>A new awaitable <see cref="Task"/></returns>
154152
protected virtual async Task BuildMessageHeadersAsync(CancellationToken cancellationToken = default)
155153
{
156-
if (this.AsyncApi == null || this.Operation == null) throw new InvalidOperationException("The executor must be initialized before execution");
157-
if (this.AsyncApi.Headers == null) return;
154+
if (this.AsyncApi == null || this.Operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution");
155+
if (this.AsyncApi.Message?.Headers == null) return;
158156
var arguments = this.GetExpressionEvaluationArguments();
159157
if (this.Authorization != null)
160158
{
161159
arguments ??= new Dictionary<string, object>();
162160
arguments.Add("authorization", this.Authorization);
163161
}
164-
this.MessageHeaders = await this.Task.Workflow.Expressions.EvaluateAsync<object>(this.AsyncApi.Headers, this.Task.Input!, arguments, cancellationToken).ConfigureAwait(false);
162+
this.MessageHeaders = await this.Task.Workflow.Expressions.EvaluateAsync<object>(this.AsyncApi.Message.Headers, this.Task.Input!, arguments, cancellationToken).ConfigureAwait(false);
165163
}
166164

167165
/// <inheritdoc/>
168166
protected override Task DoExecuteAsync(CancellationToken cancellationToken)
169167
{
170168
if (this.AsyncApi == null || this.Document == null || this.Operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution");
171-
switch (this.Operation.Value.Action)
169+
return this.Operation.Value.Action switch
172170
{
173-
case V3OperationAction.Receive:
174-
return this.DoExecutePublishOperationAsync(cancellationToken);
175-
case V3OperationAction.Send:
176-
return this.DoExecuteSubscribeOperationAsync(cancellationToken);
177-
default:
178-
throw new NotSupportedException($"The specified operation action '{this.Operation.Value.Action}' is not supported");
179-
}
171+
V3OperationAction.Receive => this.DoExecutePublishOperationAsync(cancellationToken),
172+
V3OperationAction.Send => this.DoExecuteSubscribeOperationAsync(cancellationToken),
173+
_ => throw new NotSupportedException($"The specified operation action '{this.Operation.Value.Action}' is not supported"),
174+
};
180175
}
181176

182177
/// <summary>
@@ -195,6 +190,7 @@ protected virtual async Task DoExecutePublishOperationAsync(CancellationToken ca
195190
};
196191
await using var result = await asyncApiClient.PublishAsync(parameters, cancellationToken).ConfigureAwait(false);
197192
if (!result.IsSuccessful) throw new Exception("Failed to execute the AsyncAPI publish operation");
193+
await this.SetResultAsync(null, this.Task.Definition.Then, cancellationToken).ConfigureAwait(false);
198194
}
199195

200196
/// <summary>
@@ -205,10 +201,31 @@ protected virtual async Task DoExecutePublishOperationAsync(CancellationToken ca
205201
protected virtual async Task DoExecuteSubscribeOperationAsync(CancellationToken cancellationToken)
206202
{
207203
if (this.AsyncApi == null || this.Document == null || this.Operation.Value == null) throw new InvalidOperationException("The executor must be initialized before execution");
204+
if (this.AsyncApi.Subscription == null) throw new NullReferenceException("The 'subscription' must be set when performing an AsyncAPI v3 subscribe operation");
208205
await using var asyncApiClient = this.AsyncApiClientFactory.CreateFor(this.Document);
209206
var parameters = new AsyncApiSubscribeOperationParameters(this.Operation.Key, this.AsyncApi.Server, this.AsyncApi.Protocol);
210207
await using var result = await asyncApiClient.SubscribeAsync(parameters, cancellationToken).ConfigureAwait(false);
211208
if (!result.IsSuccessful) throw new Exception("Failed to execute the AsyncAPI subscribe operation");
209+
if(result.Messages == null)
210+
{
211+
await this.SetResultAsync(null, this.Task.Definition.Then, cancellationToken).ConfigureAwait(false);
212+
return;
213+
}
214+
var observable = result.Messages;
215+
if (this.AsyncApi.Subscription.Consume.For != null) observable = observable.TakeUntil(Observable.Timer(this.AsyncApi.Subscription.Consume.For.ToTimeSpan()));
216+
if (this.AsyncApi.Subscription.Consume.Amount.HasValue) observable = observable.Take(this.AsyncApi.Subscription.Consume.Amount.Value);
217+
else if (!string.IsNullOrWhiteSpace(this.AsyncApi.Subscription.Consume.While)) observable = observable.Select(message => Observable.FromAsync(async () =>
218+
{
219+
var keepGoing = await this.Task.Workflow.Expressions.EvaluateConditionAsync(this.AsyncApi.Subscription.Consume.While, this.Task.Input!,this.GetExpressionEvaluationArguments(),cancellationToken).ConfigureAwait(false);
220+
return (message, keepGoing);
221+
})).Concat().TakeWhile(i => i.keepGoing).Select(i => i.message);
222+
else if (!string.IsNullOrWhiteSpace(this.AsyncApi.Subscription.Consume.Until)) observable = observable.Select(message => Observable.FromAsync(async () =>
223+
{
224+
var keepGoing = !(await this.Task.Workflow.Expressions.EvaluateConditionAsync(this.AsyncApi.Subscription.Consume.Until, this.Task.Input!, this.GetExpressionEvaluationArguments(), cancellationToken).ConfigureAwait(false));
225+
return (message, keepGoing);
226+
})).Concat().TakeWhile(i => i.keepGoing).Select(i => i.message);
227+
var messages = await observable.ToAsyncEnumerable().ToListAsync(cancellationToken).ConfigureAwait(false);
228+
await this.SetResultAsync(messages, this.Task.Definition.Then, cancellationToken).ConfigureAwait(false);
212229
}
213230

214231
}

src/runner/Synapse.Runner/Services/Executors/FunctionCallExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ protected virtual async Task<TaskDefinition> GetCustomFunctionAsync(string funct
113113
if (components.Length != 2) throw new Exception($"The specified value '{functionName}' is not a valid custom function qualified name ({{name}}:{{version}})");
114114
var name = components[0];
115115
var version = components[1];
116-
if (!SemVersion.TryParse(version, SemVersionStyles.Strict, out _)) throw new Exception($"The specified value '{version}' is not a valid semantic version 2.0");
116+
if (!Semver.SemVersion.TryParse(version, SemVersionStyles.Strict, out _)) throw new Exception($"The specified value '{version}' is not a valid semantic version 2.0");
117117
if (catalogName == SynapseDefaults.Tasks.CustomFunctions.Catalogs.Default)
118118
{
119119
var function = await this.Task.Workflow.CustomFunctions.GetAsync(name, cancellationToken).ConfigureAwait(false) ?? throw new NullReferenceException($"Failed to find the specified custom function '{name}'");

src/runner/Synapse.Runner/Synapse.Runner.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,14 @@
5252
<ItemGroup>
5353
<PackageReference Include="Docker.DotNet" Version="3.125.15" />
5454
<PackageReference Include="DynamicGrpc" Version="1.4.0" />
55-
<PackageReference Include="Google.Protobuf" Version="3.29.1" />
55+
<PackageReference Include="Google.Protobuf" Version="3.29.3" />
5656
<PackageReference Include="Grpc.Core" Version="2.46.6" />
5757
<PackageReference Include="Microsoft.Extensions.Configuration.KeyPerFile" Version="9.0.0" />
5858
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
5959
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
60-
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.22" />
60+
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.23" />
6161
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
62-
<PackageReference Include="MimeKit" Version="4.8.0" />
62+
<PackageReference Include="MimeKit" Version="4.9.0" />
6363
<PackageReference Include="Moq" Version="4.20.72" />
6464
<PackageReference Include="Neuroglia.AsyncApi.Client.Bindings.All" Version="3.0.1" />
6565
<PackageReference Include="Neuroglia.AsyncApi.DependencyInjectionExtensions" Version="3.0.1" />

tests/Synapse.IntegrationTests/Synapse.IntegrationTests.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13-
<PackageReference Include="coverlet.collector" Version="6.0.2">
13+
<PackageReference Include="coverlet.collector" Version="6.0.3">
1414
<PrivateAssets>all</PrivateAssets>
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1616
</PackageReference>
1717
<PackageReference Include="FluentAssertions" Version="7.0.0" />
1818
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.0" />
1919
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
20-
<PackageReference Include="ServerlessWorkflow.Sdk.Builders" Version="1.0.0-alpha5.2" />
20+
<PackageReference Include="ServerlessWorkflow.Sdk.Builders" Version="1.0.0-alpha6.2" />
2121
<PackageReference Include="Testcontainers" Version="4.1.0" />
22-
<PackageReference Include="xunit" Version="2.9.2" />
23-
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
22+
<PackageReference Include="xunit" Version="2.9.3" />
23+
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
2424
<PrivateAssets>all</PrivateAssets>
2525
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2626
</PackageReference>

tests/Synapse.UnitTests/Synapse.UnitTests.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<ItemGroup>
13-
<PackageReference Include="coverlet.collector" Version="6.0.2">
13+
<PackageReference Include="coverlet.collector" Version="6.0.3">
1414
<PrivateAssets>all</PrivateAssets>
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1616
</PackageReference>
@@ -22,14 +22,14 @@
2222
<PackageReference Include="Neuroglia.Data.Expressions.JQ" Version="4.18.1" />
2323
<PackageReference Include="Neuroglia.Data.Infrastructure.Memory" Version="4.18.1" />
2424
<PackageReference Include="Neuroglia.Data.Infrastructure.ResourceOriented.Redis" Version="4.18.1" />
25-
<PackageReference Include="ServerlessWorkflow.Sdk.Builders" Version="1.0.0-alpha5.2" />
26-
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.2" />
25+
<PackageReference Include="ServerlessWorkflow.Sdk.Builders" Version="1.0.0-alpha6.2" />
26+
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha6.2" />
2727
<PackageReference Include="System.Net.Http" Version="4.3.4" />
2828
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
2929
<PackageReference Include="Testcontainers" Version="4.1.0" />
30-
<PackageReference Include="xunit" Version="2.9.2" />
30+
<PackageReference Include="xunit" Version="2.9.3" />
3131
<PackageReference Include="Xunit.Gherkin.Quick" Version="4.5.0" />
32-
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
32+
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.1">
3333
<PrivateAssets>all</PrivateAssets>
3434
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
3535
</PackageReference>

0 commit comments

Comments
 (0)