Skip to content

Commit ef95e92

Browse files
authored
Merge pull request #467 from serverlessworkflow/feat-form-based-input
Updates the modal to create a new workflow instance by adding a form-based editor for the instance's input
2 parents 896b8d9 + 9f9c8d4 commit ef95e92

File tree

71 files changed

+1444
-248
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+1444
-248
lines changed

Diff for: src/api/Synapse.Api.Application/Synapse.Api.Application.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<NeutralLanguage>en</NeutralLanguage>
88
<GenerateDocumentationFile>True</GenerateDocumentationFile>
99
<VersionPrefix>1.0.0</VersionPrefix>
10-
<VersionSuffix>alpha5.3</VersionSuffix>
10+
<VersionSuffix>alpha5.7</VersionSuffix>
1111
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1212
<FileVersion>$(VersionPrefix)</FileVersion>
1313
<Authors>The Synapse Authors</Authors>

Diff for: src/api/Synapse.Api.Client.Core/Synapse.Api.Client.Core.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<NeutralLanguage>en</NeutralLanguage>
88
<GenerateDocumentationFile>True</GenerateDocumentationFile>
99
<VersionPrefix>1.0.0</VersionPrefix>
10-
<VersionSuffix>alpha5.3</VersionSuffix>
10+
<VersionSuffix>alpha5.7</VersionSuffix>
1111
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1212
<FileVersion>$(VersionPrefix)</FileVersion>
1313
<Authors>The Synapse Authors</Authors>

Diff for: src/api/Synapse.Api.Client.Http/Synapse.Api.Client.Http.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<NeutralLanguage>en</NeutralLanguage>
88
<GenerateDocumentationFile>True</GenerateDocumentationFile>
99
<VersionPrefix>1.0.0</VersionPrefix>
10-
<VersionSuffix>alpha5.3</VersionSuffix>
10+
<VersionSuffix>alpha5.7</VersionSuffix>
1111
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1212
<FileVersion>$(VersionPrefix)</FileVersion>
1313
<Authors>The Synapse Authors</Authors>
@@ -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.1" />
46+
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.2" />
4747
<PackageReference Include="System.Reactive" Version="6.0.1" />
4848
</ItemGroup>
4949

Diff for: src/api/Synapse.Api.Http/Synapse.Api.Http.csproj

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<OutputType>Library</OutputType>
99
<GenerateDocumentationFile>True</GenerateDocumentationFile>
1010
<VersionPrefix>1.0.0</VersionPrefix>
11-
<VersionSuffix>alpha5.3</VersionSuffix>
11+
<VersionSuffix>alpha5.7</VersionSuffix>
1212
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1313
<FileVersion>$(VersionPrefix)</FileVersion>
1414
<Authors>The Synapse Authors</Authors>
@@ -43,8 +43,8 @@
4343
</ItemGroup>
4444

4545
<ItemGroup>
46-
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.16.2" />
47-
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.16.2" />
46+
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.18.0" />
47+
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.18.0" />
4848
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.1.0" />
4949
</ItemGroup>
5050

Diff for: src/api/Synapse.Api.Server/Synapse.Api.Server.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<NeutralLanguage>en</NeutralLanguage>
88
<GenerateDocumentationFile>True</GenerateDocumentationFile>
99
<VersionPrefix>1.0.0</VersionPrefix>
10-
<VersionSuffix>alpha5.3</VersionSuffix>
10+
<VersionSuffix>alpha5.7</VersionSuffix>
1111
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1212
<FileVersion>$(VersionPrefix)</FileVersion>
1313
<Authors>The Synapse Authors</Authors>

Diff for: src/cli/Synapse.Cli/Synapse.Cli.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<NeutralLanguage>en</NeutralLanguage>
99
<GenerateDocumentationFile>True</GenerateDocumentationFile>
1010
<VersionPrefix>1.0.0</VersionPrefix>
11-
<VersionSuffix>alpha5.3</VersionSuffix>
11+
<VersionSuffix>alpha5.7</VersionSuffix>
1212
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1313
<FileVersion>$(VersionPrefix)</FileVersion>
1414
<Authors>The Synapse Authors</Authors>
@@ -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.1" />
36+
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.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>

Diff for: src/core/Synapse.Core.Infrastructure.Containers.Docker/Synapse.Core.Infrastructure.Containers.Docker.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<NeutralLanguage>en</NeutralLanguage>
88
<GenerateDocumentationFile>True</GenerateDocumentationFile>
99
<VersionPrefix>1.0.0</VersionPrefix>
10-
<VersionSuffix>alpha5.3</VersionSuffix>
10+
<VersionSuffix>alpha5.7</VersionSuffix>
1111
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1212
<FileVersion>$(VersionPrefix)</FileVersion>
1313
<Authors>The Synapse Authors</Authors>

Diff for: src/core/Synapse.Core.Infrastructure.Containers.Kubernetes/Synapse.Core.Infrastructure.Containers.Kubernetes.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<NeutralLanguage>en</NeutralLanguage>
88
<GenerateDocumentationFile>True</GenerateDocumentationFile>
99
<VersionPrefix>1.0.0</VersionPrefix>
10-
<VersionSuffix>alpha5.3</VersionSuffix>
10+
<VersionSuffix>alpha5.7</VersionSuffix>
1111
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
1212
<FileVersion>$(VersionPrefix)</FileVersion>
1313
<Authors>The Synapse Authors</Authors>

Diff for: src/runner/Synapse.Runner/AuthorizationInfo.cs renamed to src/core/Synapse.Core.Infrastructure/AuthorizationInfo.cs

+8-4
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14+
using Microsoft.Extensions.DependencyInjection;
15+
using Microsoft.Extensions.Logging;
1416
using Neuroglia.Data.Infrastructure.ResourceOriented;
17+
using ServerlessWorkflow.Sdk;
1518
using ServerlessWorkflow.Sdk.Models.Authentication;
19+
using Synapse.Core.Infrastructure.Services;
1620
using System.Text;
1721

1822
namespace Synapse;
@@ -41,28 +45,28 @@ public class AuthorizationInfo(string scheme, string parameter)
4145
/// <summary>
4246
/// Creates a new <see cref="AuthorizationInfo"/> based on the specified <see cref="AuthenticationPolicyDefinition"/>
4347
/// </summary>
44-
/// <param name="workflow">The <see cref="WorkflowDefinition"/> that defines the <see cref="AuthenticationPolicyDefinition"/> to create a new <see cref="AuthorizationInfo"/> for</param>
4548
/// <param name="authentication">The <see cref="AuthenticationPolicyDefinition"/> to create a new <see cref="AuthorizationInfo"/> for</param>
4649
/// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
50+
/// <param name="workflow">The <see cref="WorkflowDefinition"/>, if any, that defines the <see cref="AuthenticationPolicyDefinition"/> to create a new <see cref="AuthorizationInfo"/> for</param>
4751
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
4852
/// <returns>A new <see cref="AuthorizationInfo"/> based on the specified <see cref="AuthenticationPolicyDefinition"/></returns>
49-
public static async Task<AuthorizationInfo> CreateAsync(WorkflowDefinition workflow, AuthenticationPolicyDefinition authentication, IServiceProvider serviceProvider, CancellationToken cancellationToken = default)
53+
public static async Task<AuthorizationInfo> CreateAsync( AuthenticationPolicyDefinition authentication, IServiceProvider serviceProvider, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default)
5054
{
5155
ArgumentNullException.ThrowIfNull(authentication);
5256
ArgumentNullException.ThrowIfNull(serviceProvider);
5357
string scheme, parameter;
5458
var logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("AuthenticationPolicyHandler");
5559
if (!string.IsNullOrWhiteSpace(authentication.Use))
5660
{
57-
if (workflow.Use?.Authentications?.TryGetValue(authentication.Use, out AuthenticationPolicyDefinition? referencedAuthentication) != true || referencedAuthentication == null) throw new NullReferenceException($"Failed to find the specified authentication policy '{authentication.Use}'");
61+
if (workflow?.Use?.Authentications?.TryGetValue(authentication.Use, out AuthenticationPolicyDefinition? referencedAuthentication) != true || referencedAuthentication == null) throw new NullReferenceException($"Failed to find the specified authentication policy '{authentication.Use}'");
5862
else authentication = referencedAuthentication;
5963
}
6064
var isSecretBased = authentication.TryGetBaseSecret(out var secretName);
6165
object? authenticationProperties = null;
6266
if (isSecretBased && !string.IsNullOrWhiteSpace(secretName))
6367
{
6468
logger.LogDebug("Authentication is secret based");
65-
var secretsManager = serviceProvider.GetRequiredService<ISecretsManager>();
69+
var secretsManager = serviceProvider.GetService<ISecretsManager>() ?? throw new NotSupportedException("Secret based authentication is not supported in this context");
6670
var secrets = await secretsManager.GetSecretsAsync(cancellationToken).ConfigureAwait(false);
6771
if (!secrets.TryGetValue(secretName, out authenticationProperties) || authenticationProperties == null)
6872
{

Diff for: src/runner/Synapse.Runner/Extensions/HttpClientExtensions.cs renamed to src/core/Synapse.Core.Infrastructure/Extensions/HttpClientExtensions.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@ public static class HttpClientExtensions
2525
/// Configures the <see cref="HttpClient"/> to use the specified authentication mechanism
2626
/// </summary>
2727
/// <param name="httpClient">The <see cref="HttpClient"/> to configure</param>
28-
/// <param name="workflow">The <see cref="WorkflowDefinition"/> that defines the authentication to configure</param>
2928
/// <param name="authentication">An object that describes the authentication mechanism to use</param>
3029
/// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
30+
/// <param name="workflow">The <see cref="WorkflowDefinition"/>, if any, that defines the authentication to configure</param>
3131
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
3232
/// <returns>A new awaitable <see cref="Task"/></returns>
33-
public static async Task ConfigureAuthenticationAsync(this HttpClient httpClient, WorkflowDefinition workflow, AuthenticationPolicyDefinition? authentication, IServiceProvider serviceProvider, CancellationToken cancellationToken = default)
33+
public static async Task ConfigureAuthenticationAsync(this HttpClient httpClient, AuthenticationPolicyDefinition? authentication, IServiceProvider serviceProvider, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default)
3434
{
35-
ArgumentNullException.ThrowIfNull(workflow);
35+
ArgumentNullException.ThrowIfNull(serviceProvider);
3636
if (authentication == null) return;
37-
var authorization = await AuthorizationInfo.CreateAsync(workflow, authentication, serviceProvider, cancellationToken).ConfigureAwait(false);
37+
var authorization = await AuthorizationInfo.CreateAsync(authentication, serviceProvider, workflow, cancellationToken).ConfigureAwait(false);
3838
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authorization.Scheme, authorization.Parameter);
3939
}
4040

Diff for: src/core/Synapse.Core.Infrastructure/Extensions/IServiceCollectionExtensions.cs

+6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using Neuroglia.Plugins;
2424
using Neuroglia.Security.Services;
2525
using Neuroglia.Serialization;
26+
using Neuroglia.Serialization.Xml;
2627
using ServerlessWorkflow.Sdk.IO;
2728
using Synapse.Core.Infrastructure.Services;
2829
using Synapse.Resources;
@@ -45,6 +46,7 @@ public static IServiceCollection AddSynapse(this IServiceCollection services, IC
4546
{
4647
services.AddHttpClient();
4748
services.AddSerialization();
49+
services.AddSingleton<IXmlSerializer, XmlSerializer>();
4850
services.AddMediator();
4951
services.AddScoped<IUserInfoProvider, UserInfoProvider>();
5052
services.AddServerlessWorkflowIO();
@@ -61,8 +63,12 @@ public static IServiceCollection AddSynapse(this IServiceCollection services, IC
6163
services.AddSingleton<ITextDocumentRepository<string>, RedisTextDocumentRepository<string>>();
6264
services.AddSingleton<ITextDocumentRepository>(provider => provider.GetRequiredService<ITextDocumentRepository<string>>());
6365

66+
services.AddSingleton<IExternalResourceProvider, ExternalResourceProvider>();
67+
services.AddSingleton<IOAuth2TokenManager, OAuth2TokenManager>();
6468
services.AddSingleton<ISchemaHandlerProvider, SchemaHandlerProvider>();
69+
services.AddSingleton<ISchemaHandler, AvroSchemaHandler>();
6570
services.AddSingleton<ISchemaHandler, JsonSchemaHandler>();
71+
services.AddSingleton<ISchemaHandler, XmlSchemaHandler>();
6672

6773
services.AddScoped<IResourceRepository, ResourceRepository>();
6874
services.AddScoped<IAdmissionControl, AdmissionControl>();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright © 2024-Present The Synapse Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"),
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
using Neuroglia;
15+
using Neuroglia.Serialization;
16+
using ServerlessWorkflow.Sdk;
17+
using System.Net;
18+
using Avro.IO;
19+
using Avro;
20+
using Avro.Generic;
21+
22+
namespace Synapse.Core.Infrastructure.Services;
23+
24+
/// <summary>
25+
/// Represents the <see cref="ISchemaHandler"/> implementation used to handle Avro schemas
26+
/// </summary>
27+
/// <param name="externalResourceProvider">The service used to provide external resources</param>
28+
/// <param name="jsonSerializer">The service used to serialize/deserialize data to/from JSON</param>
29+
public class AvroSchemaHandler(IExternalResourceProvider externalResourceProvider, IJsonSerializer jsonSerializer)
30+
: ISchemaHandler
31+
{
32+
33+
/// <summary>
34+
/// Gets the service used to provide external resources
35+
/// </summary>
36+
protected IExternalResourceProvider ExternalResourceProvider { get; } = externalResourceProvider;
37+
38+
/// <summary>
39+
/// Gets the service used to serialize/deserialize data to/from JSON
40+
/// </summary>
41+
protected IJsonSerializer JsonSerializer { get; } = jsonSerializer;
42+
43+
/// <inheritdoc/>
44+
public virtual bool Supports(string format) => format.Equals(SchemaFormat.Avro, StringComparison.OrdinalIgnoreCase);
45+
46+
/// <inheritdoc/>
47+
public virtual async Task<IOperationResult> ValidateAsync(object graph, SchemaDefinition schema, CancellationToken cancellationToken = default)
48+
{
49+
ArgumentNullException.ThrowIfNull(graph);
50+
ArgumentNullException.ThrowIfNull(schema);
51+
if (!this.Supports(schema.Format)) throw new NotSupportedException($"The specified schema format '{schema.Format}' is not supported in this context");
52+
Schema avroSchema;
53+
try
54+
{
55+
var json = string.Empty;
56+
if (schema.Resource == null)
57+
{
58+
json = this.JsonSerializer.SerializeToText(schema.Document);
59+
}
60+
else
61+
{
62+
using var stream = await this.ExternalResourceProvider.ReadAsync(schema.Resource, cancellationToken: cancellationToken).ConfigureAwait(false);
63+
using var streamReader = new StreamReader(stream);
64+
json = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
65+
}
66+
avroSchema = Schema.Parse(json);
67+
}
68+
catch (Exception ex)
69+
{
70+
return new OperationResult((int)HttpStatusCode.BadRequest, null, new Neuroglia.Error()
71+
{
72+
Type = ErrorType.Validation,
73+
Title = ErrorTitle.Validation,
74+
Status = ErrorStatus.Validation,
75+
Detail = $"An error occurred while parsing the specified schema: {ex}"
76+
});
77+
}
78+
byte[] avroData;
79+
try
80+
{
81+
using var memoryStream = new MemoryStream();
82+
var writer = new BinaryEncoder(memoryStream);
83+
var datumWriter = new GenericDatumWriter<object>(avroSchema);
84+
var avroObject = ConvertToAvroCompatible(graph, avroSchema);
85+
datumWriter.Write(avroObject, writer);
86+
avroData = memoryStream.ToArray();
87+
}
88+
catch (Exception ex)
89+
{
90+
return new OperationResult((int)HttpStatusCode.BadRequest, null, new Neuroglia.Error()
91+
{
92+
Type = ErrorType.Validation,
93+
Title = ErrorTitle.Validation,
94+
Status = ErrorStatus.Validation,
95+
Detail = $"An error occurred while serializing the specified data to Avro: {ex}"
96+
});
97+
}
98+
try
99+
{
100+
using var memoryStream = new MemoryStream(avroData);
101+
var reader = new BinaryDecoder(memoryStream);
102+
var datumReader = new GenericDatumReader<GenericRecord>(avroSchema, avroSchema);
103+
datumReader.Read(null!, reader);
104+
return await Task.FromResult(new OperationResult((int)HttpStatusCode.OK));
105+
}
106+
catch (Exception ex)
107+
{
108+
return new OperationResult((int)HttpStatusCode.BadRequest, null, new Neuroglia.Error()
109+
{
110+
Type = ErrorType.Validation,
111+
Title = ErrorTitle.Validation,
112+
Status = ErrorStatus.Validation,
113+
Detail = ex.Message
114+
});
115+
}
116+
}
117+
118+
object ConvertToAvroCompatible(object graph, Schema schema)
119+
{
120+
return schema switch
121+
{
122+
RecordSchema recordSchema => ConvertToGenericRecord(graph, recordSchema),
123+
ArraySchema arraySchema => ConvertToArray(graph, arraySchema),
124+
MapSchema mapSchema => ConvertToMap(graph, mapSchema),
125+
UnionSchema unionSchema => ConvertToUnion(graph, unionSchema),
126+
FixedSchema fixedSchema => graph,
127+
EnumSchema enumSchema => graph,
128+
PrimitiveSchema primitiveSchema => graph,
129+
_ => throw new NotSupportedException($"Unsupported schema type: {schema.GetType().Name}")
130+
};
131+
}
132+
133+
GenericRecord ConvertToGenericRecord(object graph, RecordSchema recordSchema)
134+
{
135+
var record = new GenericRecord(recordSchema);
136+
var properties = graph.GetType().GetProperties();
137+
foreach (var field in recordSchema.Fields)
138+
{
139+
var property = properties.FirstOrDefault(p => p.Name.Equals(field.Name, StringComparison.OrdinalIgnoreCase));
140+
if (property != null)
141+
{
142+
var value = property.GetValue(graph)!;
143+
record.Add(field.Name, ConvertToAvroCompatible(value, field.Schema));
144+
}
145+
}
146+
return record;
147+
}
148+
149+
object ConvertToArray(object graph, ArraySchema arraySchema)
150+
{
151+
if (graph is not IEnumerable<object> enumerable) throw new InvalidOperationException("Provided object is not an array or enumerable");
152+
return enumerable.Select(item => ConvertToAvroCompatible(item, arraySchema.ItemSchema)).ToList();
153+
}
154+
155+
object ConvertToMap(object graph, MapSchema mapSchema)
156+
{
157+
if (graph is not IDictionary<string, object> dictionary) throw new InvalidOperationException("Provided object is not a map or dictionary");
158+
return dictionary.ToDictionary(kvp => kvp.Key, kvp => ConvertToAvroCompatible(kvp.Value, mapSchema.ValueSchema));
159+
}
160+
161+
object ConvertToUnion(object graph, UnionSchema unionSchema)
162+
{
163+
foreach (var schema in unionSchema.Schemas)
164+
{
165+
try
166+
{
167+
return ConvertToAvroCompatible(graph, schema);
168+
}
169+
catch
170+
{
171+
// Continue to next schema in the union
172+
}
173+
}
174+
throw new InvalidOperationException("Provided object does not match any schema in the union.");
175+
}
176+
177+
}

0 commit comments

Comments
 (0)