Merge pull request #6301 from elsa-workflows/bug/6299

Fix array type handling
This commit is contained in:
Sipke Schoorstra 2025-01-15 11:19:12 +01:00 committed by GitHub
commit 7b45811ef4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 28 additions and 32 deletions

View file

@ -23,10 +23,10 @@ namespace Elsa.Http;
public class HttpEndpoint : Trigger<HttpRequest>
{
internal const string HttpContextInputKey = "HttpContext";
internal const string RequestPathInputKey = "RequestPath";
internal const string PathInputKey = "Path";
/// <inheritdoc />
public HttpEndpoint([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line)
public HttpEndpoint([CallerFilePath] string? source = null, [CallerLineNumber] int? line = null) : base(source, line)
{
}
@ -38,7 +38,7 @@ public class HttpEndpoint : Trigger<HttpRequest>
UIHint = InputUIHints.SingleLine,
UIHandler = typeof(HttpEndpointPathUIHandler)
)]
public Input<string> Path { get; set; } = default!;
public Input<string> Path { get; set; } = null!;
/// <summary>
/// The HTTP methods to accept.
@ -65,37 +65,37 @@ public class HttpEndpoint : Trigger<HttpRequest>
/// The maximum time allowed to process the request.
/// </summary>
[Input(Description = "The maximum time allowed to process the request.", Category = "Upload")]
public Input<TimeSpan?> RequestTimeout { get; set; } = default!;
public Input<TimeSpan?> RequestTimeout { get; set; } = null!;
/// <summary>
/// The maximum request size allowed in bytes.
/// </summary>
[Input(Description = "The maximum request size allowed in bytes.", Category = "Upload")]
public Input<long?> RequestSizeLimit { get; set; } = default!;
public Input<long?> RequestSizeLimit { get; set; } = null!;
/// <summary>
/// The maximum request size allowed in bytes.
/// </summary>
[Input(Description = "The maximum file size allowed in bytes for an individual file.", Category = "Upload")]
public Input<long?> FileSizeLimit { get; set; } = default!;
public Input<long?> FileSizeLimit { get; set; } = null!;
/// <summary>
/// The allowed file extensions,
/// </summary>
[Input(Description = "Only file extensions in this list are allowed. Leave empty to allow all extensions", Category = "Upload", UIHint = InputUIHints.MultiText)]
public Input<ICollection<string>> AllowedFileExtensions { get; set; } = default!;
public Input<ICollection<string>> AllowedFileExtensions { get; set; } = null!;
/// <summary>
/// The allowed file extensions,
/// </summary>
[Input(Description = "File extensions in this list are forbidden. Leave empty to not block any extension.", Category = "Upload", UIHint = InputUIHints.MultiText)]
public Input<ICollection<string>> BlockedFileExtensions { get; set; } = default!;
public Input<ICollection<string>> BlockedFileExtensions { get; set; } = null!;
/// <summary>
/// The allowed file extensions,
/// </summary>
[Input(Description = "Only MIME types in this list are allowed. Leave empty to allow all types", Category = "Upload", UIHint = InputUIHints.MultiText)]
public Input<ICollection<string>> AllowedMimeTypes { get; set; } = default!;
public Input<ICollection<string>> AllowedMimeTypes { get; set; } = null!;
/// <summary>
/// A value indicating whether to expose the "Request too large" outcome.
@ -125,31 +125,31 @@ public class HttpEndpoint : Trigger<HttpRequest>
/// The parsed request content, if any.
/// </summary>
[Output(Description = "The parsed request content, if any.")]
public Output<object?> ParsedContent { get; set; } = default!;
public Output<object?> ParsedContent { get; set; } = null!;
/// <summary>
/// The uploaded files, if any.
/// </summary>
[Output(Description = "The uploaded files, if any.", IsSerializable = false)]
public Output<IFormFile[]> Files { get; set; } = default!;
public Output<IFormFile[]> Files { get; set; } = null!;
/// <summary>
/// The parsed route data, if any.
/// </summary>
[Output(Description = "The parsed route data, if any.")]
public Output<IDictionary<string, object>> RouteData { get; set; } = default!;
public Output<IDictionary<string, object>> RouteData { get; set; } = null!;
/// <summary>
/// The querystring data, if any.
/// </summary>
[Output(Description = "The querystring data, if any.")]
public Output<IDictionary<string, object>> QueryStringData { get; set; } = default!;
public Output<IDictionary<string, object>> QueryStringData { get; set; } = null!;
/// <summary>
/// The headers, if any.
/// </summary>
[Output(Description = "The headers, if any.")]
public Output<IDictionary<string, object>> Headers { get; set; } = default!;
public Output<IDictionary<string, object>> Headers { get; set; } = null!;
/// <inheritdoc />
protected override IEnumerable<object> GetTriggerPayloads(TriggerIndexingContext context) => GetBookmarkPayloads(context.ExpressionExecutionContext);
@ -205,7 +205,7 @@ public class HttpEndpoint : Trigger<HttpRequest>
context.Set(Result, request);
// Read route data, if any.
var path = context.GetWorkflowInput<PathString>(RequestPathInputKey);
var path = context.GetWorkflowInput<PathString>(PathInputKey);
var routeData = GetRouteData(httpContext, path);
var routeDictionary = routeData.Values.ToDictionary(route => route.Key, route => route.Value!);
var queryStringDictionary = httpContext.Request.Query.ToObjectDictionary();

View file

@ -8,7 +8,7 @@ namespace Elsa.Extensions;
public static class RouteExtensions
{
/// <summary>
/// Normalizes a route by ensuring a leading slash, removing any trailing slash and converting the path to lowercase.
/// Normalizes a route by ensuring a leading slash, removing any trailing slash.
/// </summary>
public static string NormalizeRoute(this string path) => $"/{path.Trim('/').ToLowerInvariant()}";
public static string NormalizeRoute(this string path) => $"/{path.Trim('/')}";
}

View file

@ -39,7 +39,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, ITenantAccessor tenan
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)")]
public async Task InvokeAsync(HttpContext httpContext, IServiceProvider serviceProvider)
{
var path = GetPath(httpContext);
var path = httpContext.Request.Path.Value!.NormalizeRoute();
var matchingPath = GetMatchingRoute(serviceProvider, path).Route;
var basePath = options.Value.BasePath?.ToString().NormalizeRoute();
@ -61,7 +61,7 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, ITenantAccessor tenan
var input = new Dictionary<string, object>
{
[HttpEndpoint.HttpContextInputKey] = true,
[HttpEndpoint.RequestPathInputKey] = path.NormalizeRoute()
[HttpEndpoint.PathInputKey] = path
};
var cancellationToken = httpContext.RequestAborted;
@ -325,8 +325,6 @@ public class HttpWorkflowsMiddleware(RequestDelegate next, ITenantAccessor tenan
}
}
private string GetPath(HttpContext httpContext) => httpContext.Request.Path.Value!.NormalizeRoute();
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize<TValue>(TValue, JsonSerializerOptions)")]
private async Task<bool> HandleMultipleWorkflowsFoundAsync(HttpContext httpContext, Func<IEnumerable<object>> workflowMatches, CancellationToken cancellationToken)
{

View file

@ -12,13 +12,11 @@ public class RouteMatcher : IRouteMatcher
/// <inheritdoc />
public RouteValueDictionary? Match(string routeTemplate, string route)
{
var normalizedRoute = route.NormalizeRoute();
var normalizedRouteTemplate = routeTemplate.NormalizeRoute();
var template = TemplateParser.Parse(normalizedRouteTemplate);
var template = TemplateParser.Parse(routeTemplate);
var matcher = new TemplateMatcher(template, GetDefaults(template));
var values = new RouteValueDictionary();
return matcher.TryMatch(normalizedRoute, values) ? values : null;
return matcher.TryMatch(route, values) ? values : null;
}
private static RouteValueDictionary GetDefaults(RouteTemplate parsedTemplate)

View file

@ -43,7 +43,7 @@ internal class Post(
var draft = !string.IsNullOrWhiteSpace(definitionId)
? await workflowDefinitionPublisher.GetDraftAsync(definitionId, VersionOptions.Latest, cancellationToken)
: default;
: null;
var isNew = draft == null;

View file

@ -44,7 +44,7 @@ public class ArgumentJsonConverter : JsonConverter<ArgumentDefinition>
var type = _wellKnownTypeRegistry.GetTypeOrDefault(typeName);
if (isArray)
type = type.MakeCollectionType();
type = type.MakeArrayType();
var newOptions = new JsonSerializerOptions(options);
newOptions.Converters.RemoveWhere(x => x is ArgumentJsonConverterFactory);

View file

@ -40,9 +40,9 @@ public class TypeJsonConverter : JsonConverter<Type>
}
// Handle collection types.
if (typeAlias.EndsWith("()"))
if (typeAlias.StartsWith("List<") && typeAlias.EndsWith(">"))
{
var elementTypeAlias = typeAlias[..^"()".Length];
var elementTypeAlias = typeAlias[5..^1];
var elementType = _wellKnownTypeRegistry.TryGetType(elementTypeAlias, out var t) ? t : Type.GetType(elementTypeAlias)!;
return typeof(List<>).MakeGenericType(elementType);
}
@ -70,7 +70,7 @@ public class TypeJsonConverter : JsonConverter<Type>
if (typedEnumerable.IsAssignableFrom(value) && _wellKnownTypeRegistry.TryGetAlias(elementType, out var elementTypeAlias))
{
writer.WriteStringValue($"{elementTypeAlias}()");
writer.WriteStringValue($"List<{elementTypeAlias}>");
return;
}
}

View file

@ -59,7 +59,7 @@ public class VariableMapper
.OnSuccess(value => variable.Value = value)
.OnFailure(e => _logger.LogWarning("Failed to convert {SourceValue} to {TargetType}", source.Value, type.Name));
variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : default;
variable.StorageDriverType = !string.IsNullOrEmpty(source.StorageDriverTypeName) ? Type.GetType(source.StorageDriverTypeName) : null;
return variable;
}
@ -76,6 +76,6 @@ public class VariableMapper
var storageDriverTypeName = source.StorageDriverType?.GetSimpleAssemblyQualifiedName();
var serializedValue = value.Format();
return new VariableModel(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName);
return new(source.Id, source.Name, valueTypeAlias, serializedValue, storageDriverTypeName);
}
}