Add project files.

This commit is contained in:
2025-08-18 17:11:43 +03:30
parent 807dc263be
commit e5eb10d604
3943 changed files with 223719 additions and 0 deletions
@@ -0,0 +1,56 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Api.Client.Extensions;
using JetBrains.Annotations;
namespace Elsa.Api.Client.Converters;
/// <summary>
/// Converts <see cref="Type"/> objects to and from their assembly-qualified name.
/// </summary>
[UsedImplicitly]
public class TypeJsonConverter : JsonConverter<Type>
{
/// <inheritdoc />
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(Type) || typeToConvert.FullName == "System.RuntimeType";
}
/// <inheritdoc />
public override Type? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var typeName = reader.GetString()!;
// Handle collection types.
if (typeName.EndsWith("[]"))
{
var elementTypeName = typeName[..^"[]".Length];
var elementType = Type.GetType(elementTypeName)!;
return typeof(List<>).MakeGenericType(elementType);
}
return Type.GetType(typeName);
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Type value, JsonSerializerOptions options)
{
// Handle collection types.
if (value is { IsGenericType: true, GenericTypeArguments.Length: 1 })
{
var elementType = value.GenericTypeArguments.First();
var typedEnumerable = typeof(IEnumerable<>).MakeGenericType(elementType);
if (typedEnumerable.IsAssignableFrom(value))
{
var elementTypeName = value.GetSimpleAssemblyQualifiedName();
JsonSerializer.Serialize(writer, $"{elementTypeName}[]", options);
return;
}
}
var typeName = value.GetSimpleAssemblyQualifiedName();
JsonSerializer.Serialize(writer, typeName, options);
}
}
@@ -0,0 +1,24 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Converters;
/// <summary>
/// A JSON converter that serializes <see cref="VersionOptions"/>.
/// </summary>
public class VersionOptionsJsonConverter : JsonConverter<VersionOptions>
{
/// <inheritdoc />
public override VersionOptions Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var textValue = reader.GetString();
return string.IsNullOrWhiteSpace(textValue) ? VersionOptions.Published : VersionOptions.FromString(textValue);
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, VersionOptions value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
@@ -0,0 +1,17 @@
using System.ComponentModel;
using System.Globalization;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Converters;
/// <summary>
/// A type converter that converts <see cref="VersionOptions"/> to and from strings.
/// </summary>
public class VersionOptionsTypeConverter : TypeConverter
{
/// <inheritdoc />
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) => sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
/// <inheritdoc />
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) => value is string text ? VersionOptions.FromString(text) : base.ConvertFrom(context, culture, value)!;
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>
Provides HTTP clients for the Elsa Workflows REST API.
</Description>
<PackageTags>elsa api client</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" />
<PackageReference Include="Polly" />
<PackageReference Include="Polly.Extensions.Http" />
<PackageReference Include="Refit" />
<PackageReference Include="Refit.HttpClientFactory" />
</ItemGroup>
</Project>
@@ -0,0 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=helpers/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resources_005Clogpersistencestrategies_005Ccontracts/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=resources_005Clogpersistencestrategies_005Cmodels/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -0,0 +1,216 @@
using System.Text.Json.Nodes;
using Elsa.Api.Client.Resources.Resilience.Models;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Models;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for <see cref="JsonObject"/>.
/// </summary>
public static class ActivityExtensions
{
/// <summary>
/// Gets the type name of the specified activity.
/// </summary>
public static string GetTypeName(this JsonObject activity) => activity.GetProperty<string>("type")!;
/// <summary>
/// Gets the type name of the specified activity.
/// </summary>
public static void SetTypeName(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "type");
/// <summary>
/// Gets the version of the specified activity.
/// </summary>
public static int GetVersion(this JsonObject activity) => activity.GetProperty<int>("version");
/// <summary>
/// Gets the version of the specified activity.
/// </summary>
public static void SetVersion(this JsonObject activity, int value) => activity.SetProperty(JsonValue.Create(value), "version");
/// <summary>
/// Gets the ID of the specified activity.
/// </summary>
public static string GetId(this JsonObject activity) => activity.GetProperty<string>("id")!;
/// <summary>
/// Sets the ID of the specified activity.
/// </summary>
public static void SetId(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "id");
/// <summary>
/// Gets the node ID of the specified activity.
/// </summary>
public static string GetNodeId(this JsonObject activity) => activity.GetProperty<string>("nodeId")!;
/// <summary>
/// Sets the node ID of the specified activity.
/// </summary>
public static void SetNodeId(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "nodeId");
/// <summary>
/// Gets the name of the specified activity.
/// </summary>
public static string? GetName(this JsonObject activity) => activity.GetProperty<string>("name");
/// <summary>
/// Sets the name of the specified activity.
/// </summary>
public static void SetName(this JsonObject activity, string? value) => activity.SetProperty(JsonValue.Create(value), "name");
/// <summary>
/// Gets the designer metadata for the specified activity.
/// </summary>
public static JsonObject? GetMetadata(this JsonObject activity) => activity.GetProperty("metadata")?.AsObject();
/// <summary>
/// Sets the designer metadata for the specified activity.
/// </summary>
public static void SetDesignerMetadata(this JsonObject activity, ActivityDesignerMetadata designerMetadata) => activity.SetProperty(designerMetadata.SerializeToNode(), "metadata", "designer");
/// <summary>
/// Gets the designer metadata for the specified activity.
/// </summary>
public static ActivityDesignerMetadata GetDesignerMetadata(this JsonObject activity) => activity.GetProperty<ActivityDesignerMetadata>("metadata", "designer") ?? new ActivityDesignerMetadata();
/// <summary>
/// Gets the display text for the specified activity.
/// </summary>
public static string? GetDisplayText(this JsonObject activity) => activity.GetProperty("metadata", "displayText")?.GetValue<string>();
/// <summary>
/// Sets the display text for the specified activity.
/// </summary>
public static void SetDisplayText(this JsonObject activity, string? value)
{
activity.SetProperty(JsonValue.Create(value), "metadata", "displayText");
}
/// <summary>
/// Gets the description for the specified activity.
/// </summary>
public static string? GetDescription(this JsonObject activity) => activity.GetProperty("metadata", "description")?.GetValue<string>();
/// <summary>
/// Sets the description for the specified activity.
/// </summary>
public static void SetDescription(this JsonObject activity, string value) => activity.SetProperty(JsonValue.Create(value), "metadata", "description");
/// <summary>
/// Gets a value indicating whether the description for the specified activity should be shown.
/// </summary>
public static bool? GetShowDescription(this JsonObject activity) => activity.GetProperty<bool>("metadata", "showDescription");
/// <summary>
/// Sets a value indicating whether the description for the specified activity should be shown.
/// </summary>
public static void SetShowDescription(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "metadata", "showDescription");
/// <summary>
/// Gets a value indicating whether the specified activity can trigger the workflow.
/// </summary>
public static bool? GetCanStartWorkflow(this JsonObject activity) => activity.GetProperty<bool>("customProperties", "canStartWorkflow");
/// <summary>
/// Sets a value indicating whether the specified activity can trigger the workflow.
/// </summary>
public static void SetCanStartWorkflow(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "customProperties", "canStartWorkflow");
/// <summary>
/// Gets the activities in the specified flowchart.
/// </summary>
public static IEnumerable<JsonObject> GetActivities(this JsonObject flowchart) => flowchart.GetProperty("activities")?.AsArray().AsEnumerable().Cast<JsonObject>() ?? Array.Empty<JsonObject>();
/// <summary>
/// Sets the activities in the specified flowchart.
/// </summary>
public static void SetActivities(this JsonObject flowchart, JsonArray activities) => flowchart.SetProperty(activities, "activities");
/// <summary>
/// Gets the connections in the specified flowchart.
/// </summary>
public static IEnumerable<Connection> GetConnections(this JsonObject flowchart) => flowchart.GetProperty<ICollection<Connection>>("connections") ?? new List<Connection>();
/// <summary>
/// Sets the connections in the specified flowchart.
/// </summary>
public static void SetConnections(this JsonObject flowchart, IEnumerable<Connection> connections) => flowchart.SetProperty(JsonValue.Create(connections), "connections");
/// <summary>
/// Gets the root activity in the specified activity.
/// </summary>
public static JsonObject? GetRoot(this JsonObject activity) => activity.GetProperty("root") as JsonObject;
/// <summary>
/// Sets the root activity in the specified activity.
/// </summary>
public static void SetRoot(this JsonObject container, JsonObject root) => container.SetProperty(root, "root");
/// <summary>
/// Gets the root activity in the specified activity.
/// </summary>
public static string? GetWorkflowDefinitionId(this JsonObject activity)
{
// Determine if this activity represents a workflow definition activity based on the presence of the following properties.
var workflowDefinitionIdNode = activity.GetProperty("workflowDefinitionId");
var workflowDefinitionVersionId = activity.GetProperty("workflowDefinitionVersionId");
var latestAvailablePublishedVersion = activity.GetProperty("latestAvailablePublishedVersion");
var latestAvailablePublishedVersionId = activity.GetProperty("latestAvailablePublishedVersionId");
if (workflowDefinitionIdNode == null || workflowDefinitionVersionId == null || latestAvailablePublishedVersion == null || latestAvailablePublishedVersionId == null)
return null; // Not a workflow definition activity.
if (workflowDefinitionIdNode is JsonValue value)
return value.ToString();
return null; // Not a workflow definition activity.
}
/// <summary>
/// Determines whether the given activity is a workflow definition activity.
/// </summary>
/// <param name="activity">The JsonObject representing the activity.</param>
/// <returns>Returns true if the activity is a workflow definition activity; otherwise, false.</returns>
public static bool GetIsWorkflowDefinitionActivity(this JsonObject activity) => activity.ContainsKey("workflowDefinitionId") && activity.ContainsKey("workflowDefinitionVersionId");
/// <summary>
/// Gets a value indicating whether the specified activity can trigger the workflow.
/// </summary>
public static bool? GetRunAsynchronously(this JsonObject activity) => activity.GetProperty<bool>("customProperties", "runAsynchronously");
/// <summary>
/// Sets a value indicating whether the specified activity can trigger the workflow.
/// </summary>
public static void SetRunAsynchronously(this JsonObject activity, bool value) => activity.SetProperty(JsonValue.Create(value), "customProperties", "runAsynchronously");
/// <summary>
/// Gets the commit state behavior for the specified activity.
/// </summary>
public static string? GetCommitStrategy(this JsonObject activity) => activity.TryGetProperty<string?>("customProperties", "commitStrategyName");
/// <summary>
/// Sets the commit state behavior for the specified activity.
/// </summary>
public static void SetCommitStrategy(this JsonObject activity, string? name) => activity.SetProperty(JsonValue.Create(name), "customProperties", "commitStrategyName");
/// <summary>
/// Gets the resilience strategy for the specified activity.
/// </summary>
public static ResilienceStrategyConfig? GetResilienceStrategy(this JsonObject activity)
{
var node = activity.GetProperty("customProperties", "resilienceStrategy");
return ResilienceStrategyConfig.Deserialize(node);
}
/// <summary>
/// Sets the resilience strategy for the specified activity.
/// </summary>
public static void SetResilienceStrategy(this JsonObject activity, ResilienceStrategyConfig? config)
{
var node = config?.SerializeToNode();
activity.SetProperty(node, "customProperties", "resilienceStrategy");
}
}
@@ -0,0 +1,28 @@
using Refit;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for <see cref="IApiResponse"/>.
/// </summary>
public static class ApiResponseExtensions
{
/// <summary>
/// Returns the downloaded file name or the specified default file name.
/// </summary>
public static string GetDownloadedFileNameOrDefault(this IApiResponse response, string defaultFileName = "download.bin")
{
var fileName = defaultFileName;
if (response.Headers.TryGetValues("content-disposition", out var contentDispositionHeader)) // Only available if the Elsa Server exposes the "Content-Disposition" header.
{
// ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract
var values = contentDispositionHeader.ToList() ?? [];
if (values.Count >= 2)
fileName = values[1].Split('=')[1];
}
return fileName;
}
}
@@ -0,0 +1,186 @@
using Elsa.Api.Client.Options;
using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts;
using Elsa.Api.Client.Resources.ActivityDescriptors.Contracts;
using Elsa.Api.Client.Resources.ActivityExecutions.Contracts;
using Elsa.Api.Client.Resources.Alterations.Contracts;
using Elsa.Api.Client.Resources.CommitStrategies.Contracts;
using Elsa.Api.Client.Resources.Features.Contracts;
using Elsa.Api.Client.Resources.Identity.Contracts;
using Elsa.Api.Client.Resources.IncidentStrategies.Contracts;
using Elsa.Api.Client.Resources.LogPersistenceStrategies;
using Elsa.Api.Client.Resources.Resilience.Contracts;
using Elsa.Api.Client.Resources.Scripting.Contracts;
using Elsa.Api.Client.Resources.StorageDrivers.Contracts;
using Elsa.Api.Client.Resources.Tasks.Contracts;
using Elsa.Api.Client.Resources.VariableTypes.Contracts;
using Elsa.Api.Client.Resources.WorkflowActivationStrategies.Contracts;
using Elsa.Api.Client.Resources.WorkflowDefinitions.Contracts;
using Elsa.Api.Client.Resources.WorkflowExecutionContexts.Contracts;
using Elsa.Api.Client.Resources.WorkflowInstances.Contracts;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Refit;
using static Elsa.Api.Client.RefitSettingsHelper;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for dependency injection.
/// </summary>
[PublicAPI]
public static class DependencyInjectionExtensions
{
/// <summary>
/// Adds default Elsa API clients configured to use an API key.
/// </summary>
public static IServiceCollection AddDefaultApiClientsUsingApiKey(this IServiceCollection services, Action<ElsaClientOptions> configureOptions)
{
var options = new ElsaClientOptions();
configureOptions(options);
return services.AddDefaultApiClients(client =>
{
client.BaseAddress = options.BaseAddress;
client.ApiKey = options.ApiKey;
client.ConfigureHttpClient = options.ConfigureHttpClient;
});
}
/// <summary>
/// Adds default Elsa API clients.
/// </summary>
public static IServiceCollection AddDefaultApiClients(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null)
{
return services.AddApiClients(configureClient, builderOptions =>
{
var builderOptionsWithoutRetryPolicy = new ElsaClientBuilderOptions
{
ApiKey = builderOptions.ApiKey,
AuthenticationHandler = builderOptions.AuthenticationHandler,
BaseAddress = builderOptions.BaseAddress,
ConfigureHttpClient = builderOptions.ConfigureHttpClient,
ConfigureHttpClientBuilder = builderOptions.ConfigureHttpClientBuilder,
ConfigureRetryPolicy = null
};
services.AddApi<IWorkflowDefinitionsApi>(builderOptions);
services.AddApi<IExecuteWorkflowApi>(builderOptionsWithoutRetryPolicy);
services.AddApi<IWorkflowInstancesApi>(builderOptions);
services.AddApi<IActivityDescriptorsApi>(builderOptions);
services.AddApi<IActivityDescriptorOptionsApi>(builderOptions);
services.AddApi<IActivityExecutionsApi>(builderOptions);
services.AddApi<IStorageDriversApi>(builderOptions);
services.AddApi<IVariableTypesApi>(builderOptions);
services.AddApi<IWorkflowActivationStrategiesApi>(builderOptions);
services.AddApi<IIncidentStrategiesApi>(builderOptions);
services.AddApi<ILogPersistenceStrategiesApi>(builderOptions);
services.AddApi<ICommitStrategiesApi>(builderOptions);
services.AddApi<IResilienceStrategiesApi>(builderOptions);
services.AddApi<IRetryAttemptsApi>(builderOptions);
services.AddApi<ILoginApi>(builderOptions);
services.AddApi<IFeaturesApi>(builderOptions);
services.AddApi<IJavaScriptApi>(builderOptions);
services.AddApi<IExpressionDescriptorsApi>(builderOptions);
services.AddApi<IWorkflowContextProviderDescriptorsApi>(builderOptions);
services.AddApi<IAlterationsApi>(builderOptions);
services.AddApi<ITasksApi>(builderOptions);
});
}
/// <summary>
/// Adds an API client to the service collection. Requires AddElsaClient to be called exactly once.
/// </summary>
public static IServiceCollection AddApiClient<T>(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null) where T : class
{
return services.AddApiClients(configureClient, builderOptions => services.AddApi<T>(builderOptions));
}
/// <summary>
/// Adds the Elsa client to the service collection.
/// </summary>
public static IServiceCollection AddApiClients(this IServiceCollection services, Action<ElsaClientBuilderOptions>? configureClient = null, Action<ElsaClientBuilderOptions>? configureServices = null)
{
var builderOptions = new ElsaClientBuilderOptions();
configureClient?.Invoke(builderOptions);
builderOptions.ConfigureHttpClientBuilder += builder => builder.AddHttpMessageHandler(sp => (DelegatingHandler)sp.GetRequiredService(builderOptions.AuthenticationHandler));
services.TryAddScoped(builderOptions.AuthenticationHandler);
services.Configure<ElsaClientOptions>(options =>
{
options.BaseAddress = builderOptions.BaseAddress;
options.ConfigureHttpClient = builderOptions.ConfigureHttpClient;
options.ApiKey = builderOptions.ApiKey;
});
configureServices?.Invoke(builderOptions);
return services;
}
/// <summary>
/// Adds a refit client for the specified API type.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
/// <typeparam name="T">The type representing the API.</typeparam>
public static IServiceCollection AddApi<T>(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
{
return services.AddApi(typeof(T), httpClientBuilderOptions);
}
/// <summary>
/// Adds a refit client for the specified API type.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="apiType">The type representing the API</param>
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
public static IServiceCollection AddApi(this IServiceCollection services, Type apiType, ElsaClientBuilderOptions? httpClientBuilderOptions = default)
{
var builder = services.AddRefitClient(apiType, sp => CreateRefitSettings(sp, httpClientBuilderOptions?.ConfigureJsonSerializerOptions), apiType.Name).ConfigureHttpClient(ConfigureElsaApiHttpClient);
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
httpClientBuilderOptions?.ConfigureRetryPolicy?.Invoke(builder);
return services;
}
/// <summary>
/// Adds a refit client for the specified API type.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="httpClientBuilderOptions">An options object that can be used to configure the HTTP client builder.</param>
/// <typeparam name="T">The type representing the API.</typeparam>
public static void AddApiWithoutRetryPolicy<T>(this IServiceCollection services, ElsaClientBuilderOptions? httpClientBuilderOptions = default) where T : class
{
var builder = services
.AddRefitClient<T>(sp => CreateRefitSettings(sp), typeof(T).Name)
.ConfigureHttpClient(ConfigureElsaApiHttpClient);
httpClientBuilderOptions?.ConfigureHttpClientBuilder(builder);
}
/// <summary>
/// Creates an API client for the specified API type.
/// </summary>
public static T CreateApi<T>(this IServiceProvider serviceProvider, Uri baseAddress) where T : class
{
var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient(typeof(T).Name);
httpClient.BaseAddress = baseAddress;
return CreateApi<T>(serviceProvider, httpClient);
}
/// <summary>
/// Creates an API client for the specified API type.
/// </summary>
public static T CreateApi<T>(this IServiceProvider serviceProvider, HttpClient httpClient) where T : class
{
return RestService.For<T>(httpClient, CreateRefitSettings(serviceProvider));
}
private static void ConfigureElsaApiHttpClient(IServiceProvider serviceProvider, HttpClient httpClient)
{
var options = serviceProvider.GetRequiredService<IOptions<ElsaClientOptions>>().Value;
httpClient.BaseAddress = options.BaseAddress;
options.ConfigureHttpClient?.Invoke(serviceProvider, httpClient);
}
}
@@ -0,0 +1,66 @@
using System.Text.Json;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for <see cref="JsonElement"/>.
/// </summary>
public static class DictionaryExtensions
{
/// <summary>
/// Returns the value of the specified property if it exists, otherwise the default value.
/// </summary>
public static T TryGetValue<T>(this IDictionary<string, object> dictionary, string key, Func<T>? defaultValue = default, JsonSerializerOptions? serializerOptions = default)
{
var caseInsensitiveDictionary = ToCaseInsensitiveDictionary(dictionary);
if (caseInsensitiveDictionary.TryGetValue(key, out var value) && value is not JsonElement { ValueKind: JsonValueKind.Undefined })
{
var convertedValue = value.ConvertTo<T>(new ObjectConverterOptions(serializerOptions));
if (convertedValue != null)
return convertedValue;
}
if (defaultValue == null)
return default!;
var defaultVal = defaultValue()!;
dictionary[key] = defaultVal;
return defaultVal;
}
/// <summary>
/// Returns the value of the specified property if it exists, otherwise the default value.
/// </summary>
public static object? TryGetValue(this IDictionary<string, object> dictionary, string key, Func<object>? defaultValue = default)
{
var caseInsensitiveDictionary = ToCaseInsensitiveDictionary(dictionary);
if (caseInsensitiveDictionary.TryGetValue(key, out var value) && value is not JsonElement { ValueKind: JsonValueKind.Undefined })
return value;
if (defaultValue == null)
return default;
var defaultVal = defaultValue()!;
dictionary[key] = defaultVal;
return defaultVal;
}
// A method that de-duplicates the keys in the dictionary, taking into account that there may already be multiple keys that would be considered the same if the case was ignored.
private static Dictionary<string, object> ToCaseInsensitiveDictionary(this IDictionary<string, object> dictionary)
{
var caseInsensitiveDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
foreach (var key in dictionary.Keys)
{
if (caseInsensitiveDictionary.ContainsKey(key))
continue;
caseInsensitiveDictionary[key] = dictionary[key]!;
}
return caseInsensitiveDictionary;
}
}
@@ -0,0 +1,14 @@
using Elsa.Api.Client.Shared;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Contains extension methods for the <see cref="HttpResponseMessage"/> class.
/// </summary>
public static class HttpResponseMessageExtensions
{
/// <summary>
/// Gets the workflow instance ID from the response.
/// </summary>
public static string? GetWorkflowInstanceId(this HttpResponseMessage response) => response.Headers.TryGetValues(HeaderNames.WorkflowInstanceId, out var values) ? values.FirstOrDefault() : default;
}
@@ -0,0 +1,26 @@
using System.Text.Json;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for <see cref="JsonElement"/>.
/// </summary>
public static class JsonElementExtensions
{
/// <summary>
/// Returns the value of the specified property if it exists, otherwise the default value.
/// </summary>
public static bool TryGetPropertySafe(this JsonElement element, string propertyName, out JsonElement value)
{
value = default;
if(element.ValueKind != JsonValueKind.Object)
return false;
if (!element.TryGetProperty(propertyName, out var property))
return false;
value = property;
return true;
}
}
@@ -0,0 +1,207 @@
using System.Text.Json;
using System.Text.Json.Nodes;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for <see cref="JsonObject"/>.
/// </summary>
public static class JsonObjectExtensions
{
/// <summary>
/// Returns true if the specified <see cref="JsonObject"/> represents an activity.
/// </summary>
public static bool IsActivity(this JsonObject obj)
{
return obj.ContainsKey("type") && obj.ContainsKey("id") && obj.ContainsKey("version");
}
/// <summary>
/// Serializes the specified value to a <see cref="JsonObject"/>.
/// </summary>
/// <param name="value">The value to serialize.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
public static JsonNode SerializeToNode(this object value, JsonSerializerOptions? options = null)
{
options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
return JsonSerializer.SerializeToNode(value, options)!;
}
/// <summary>
/// Serializes the specified value to a <see cref="JsonArray"/>.
/// </summary>
/// <param name="value">The value to serialize.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
public static JsonArray SerializeToArray(this object value, JsonSerializerOptions? options = null)
{
options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
return JsonSerializer.SerializeToNode(value, options)!.AsArray();
}
/// <summary>
/// Serializes the specified value to a <see cref="JsonArray"/>.
/// </summary>
/// <param name="value">The value to serialize.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <returns>A <see cref="JsonObject"/> representing the specified value.</returns>
public static JsonArray SerializeToArray<T>(this IEnumerable<T> value, JsonSerializerOptions? options = null)
{
options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
return JsonSerializer.SerializeToNode(value, options)!.AsArray();
}
/// <summary>
/// Deserializes the specified <see cref="JsonNode"/> to the specified type.
/// </summary>
/// <param name="value">The <see cref="JsonNode"/> to deserialize.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use.</param>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The deserialized value.</returns>
public static T Deserialize<T>(this JsonNode value, JsonSerializerOptions? options = null)
{
options ??= new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
if (value is JsonObject jsonObject)
return JsonSerializer.Deserialize<T>(jsonObject, options)!;
if (value is JsonArray jsonArray)
return JsonSerializer.Deserialize<T>(jsonArray, options)!;
if (typeof(T).IsEnum || (Nullable.GetUnderlyingType(typeof(T))?.IsEnum ?? false))
{
if (value.GetValueKind() == JsonValueKind.Null)
return default!;
return (T)Enum.Parse(Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), value.ToString());
}
if (value is JsonValue jsonValue)
return jsonValue.GetValue<T>();
throw new NotSupportedException($"Cannot deserialize {value.GetType()} to {typeof(T)}.");
}
/// <summary>
/// Sets the property value of the specified model.
/// </summary>
/// <param name="model">The model to set the property value on.</param>
/// <param name="value">The value to set.</param>
/// <param name="path">The path to the property.</param>
public static void SetProperty(this JsonObject model, JsonNode? value, params string[] path)
{
model = GetPropertyContainer(model, path);
model[path.Last()] = value?.SerializeToNode();
}
/// <summary>
/// Sets the property value of the specified model.
/// </summary>
/// <param name="model">The model to set the property value on.</param>
/// <param name="value">The value to set.</param>
/// <param name="path">The path to the property.</param>
public static void SetProperty(this JsonObject model, JsonArray? value, params string[] path)
{
model = GetPropertyContainer(model, path);
model[path.Last()] = value?.SerializeToNode();
}
/// <summary>
/// Sets the property value of the specified model.
/// </summary>
/// <param name="model">The model to set the property value on.</param>
/// <param name="value">The value to set.</param>
/// <param name="path">The path to the property.</param>
public static void SetProperty(this JsonObject model, IEnumerable<JsonNode> value, params string[] path)
{
model = GetPropertyContainer(model, path);
model[path.Last()] = new JsonArray(value.Select(x => x.SerializeToNode()).ToArray());
}
/// <summary>
/// Gets the property value of the specified model.
/// </summary>
/// <param name="model">The model to get the property value from.</param>
/// <param name="path">The path to the property.</param>
/// <returns>The property value.</returns>
public static JsonNode? GetProperty(this JsonObject model, params string[] path)
{
var currentModel = model;
foreach (var prop in path.SkipLast(1))
{
if (currentModel[prop] is not JsonObject value)
return null;
currentModel = value;
}
return currentModel[path.Last()];
}
/// <summary>
/// Gets the property value of the specified model.
/// </summary>
/// <param name="model">The model to get the property value from.</param>
/// <param name="path">The path to the property.</param>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The property value.</returns>
public static T? TryGetProperty<T>(this JsonObject model, params string[] path)
{
try
{
return model.GetProperty<T>(path);
}
catch (Exception e)
{
return default;
}
}
/// <summary>
/// Gets the property value of the specified model.
/// </summary>
/// <param name="model">The model to get the property value from.</param>
/// <param name="path">The path to the property.</param>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The property value.</returns>
public static T? GetProperty<T>(this JsonObject model, params string[] path)
{
var property = GetProperty(model, path);
return property != null ? property.Deserialize<T>() : default;
}
/// <summary>
/// Gets the property value of the specified model.
/// </summary>
/// <param name="model">The model to get the property value from.</param>
/// <param name="options">The <see cref="JsonSerializerOptions"/> to use when deserializing.</param>
/// <param name="path">The path to the property.</param>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The property value.</returns>
public static T? GetProperty<T>(this JsonObject model, JsonSerializerOptions options, params string[] path)
{
var property = GetProperty(model, path);
return property != null ? property.Deserialize<T>(options) : default;
}
/// <summary>
/// Returns the property container of the specified model.
/// </summary>
/// <param name="model">The model to set the property value on.</param>
/// <param name="path">The path to the property.</param>
private static JsonObject GetPropertyContainer(this JsonObject model, params string[] path)
{
foreach (var prop in path.SkipLast(1))
{
var property = model[prop] as JsonObject ?? new JsonObject();
model[prop] = property;
model = property;
}
return model;
}
}
@@ -0,0 +1,229 @@
using System.Collections;
using System.ComponentModel;
using System.Dynamic;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides options to the conversion method.
/// </summary>
public record ObjectConverterOptions(JsonSerializerOptions? SerializerOptions = default)
{
/// <inheritdoc />
public ObjectConverterOptions(Action<JsonSerializerOptions> configureSerializerOptions) : this()
{
SerializerOptions = new JsonSerializerOptions();
configureSerializerOptions(SerializerOptions);
}
}
/// <summary>
/// A helper that attempts many strategies to try and convert the source value into the destination type.
/// </summary>
public static class ObjectConverter
{
/// <summary>
/// Attempts to convert the source value into the destination type.
/// </summary>
public static T? ConvertTo<T>(this object? value, ObjectConverterOptions? serializerOptions = null) => value != null ? (T?)value.ConvertTo(typeof(T), serializerOptions) : default;
/// <summary>
/// Attempts to convert the source value into the destination type.
/// </summary>
public static object? ConvertTo(this object? value, Type targetType, ObjectConverterOptions? converterOptions = null)
{
if (value == null)
return default!;
var sourceType = value.GetType();
if (sourceType == targetType)
return value;
var options = converterOptions?.SerializerOptions != null ? new JsonSerializerOptions(converterOptions.SerializerOptions) : new JsonSerializerOptions();
options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
options.ReferenceHandler = ReferenceHandler.Preserve;
options.PropertyNameCaseInsensitive = true;
options.Converters.Add(new JsonStringEnumConverter());
var underlyingTargetType = Nullable.GetUnderlyingType(targetType) ?? targetType;
var underlyingSourceType = Nullable.GetUnderlyingType(sourceType) ?? sourceType;
if (value is JsonElement { ValueKind: JsonValueKind.Number } jsonNumber && underlyingTargetType == typeof(string))
return jsonNumber.ToString().ConvertTo(underlyingTargetType);
if (value is JsonElement jsonElement)
{
if (jsonElement.ValueKind == JsonValueKind.String && underlyingTargetType != typeof(string))
return jsonElement.GetString().ConvertTo(underlyingTargetType);
return jsonElement.Deserialize(targetType, options);
}
if (value is JsonNode jsonNode)
return jsonNode.Deserialize(targetType, options);
if (underlyingSourceType == typeof(string) && !underlyingTargetType.IsPrimitive && underlyingTargetType != typeof(object))
{
var stringValue = (string)value;
try
{
var firstChar = stringValue.TrimStart().FirstOrDefault();
if (firstChar is '{' or '[')
return JsonSerializer.Deserialize(stringValue, underlyingTargetType, options);
}
catch (Exception)
{
throw new Exception($"Failed to deserialize {stringValue} to {underlyingTargetType}");
}
}
if (targetType == typeof(object))
return value;
if (underlyingTargetType.IsInstanceOfType(value))
return value;
if (underlyingSourceType == underlyingTargetType)
return value;
if (IsDateType(underlyingSourceType) && IsDateType(underlyingTargetType))
return ConvertAnyDateType(value, underlyingTargetType);
if (typeof(IDictionary<string, object>).IsAssignableFrom(underlyingSourceType) && underlyingTargetType.IsClass)
{
if (typeof(ExpandoObject) == underlyingTargetType)
{
var expandoJson = JsonSerializer.Serialize(value);
return ConvertTo(expandoJson, underlyingTargetType, converterOptions);
}
if (typeof(IDictionary<string, object>).IsAssignableFrom(underlyingTargetType))
return new Dictionary<string, object>((IDictionary<string, object>)value);
if (typeof(ExpandoObject) == underlyingSourceType)
{
// Parse ExpandoObject into target type.
var expandoObject = (IDictionary<string, object>)value;
var json = JsonSerializer.Serialize(expandoObject);
return ConvertTo(json, underlyingTargetType, converterOptions);
}
}
var targetTypeConverter = TypeDescriptor.GetConverter(underlyingTargetType);
if (targetTypeConverter.CanConvertFrom(underlyingSourceType))
return targetTypeConverter.IsValid(value)
? targetTypeConverter.ConvertFrom(null!, CultureInfo.InvariantCulture, value)
: targetType.GetDefaultValue();
var sourceTypeConverter = TypeDescriptor.GetConverter(underlyingSourceType);
if (sourceTypeConverter.CanConvertTo(underlyingTargetType))
return sourceTypeConverter.ConvertTo(value, underlyingTargetType);
if (underlyingTargetType.IsEnum)
{
if (underlyingSourceType == typeof(string))
return Enum.Parse(underlyingTargetType, (string)value);
if (underlyingSourceType == typeof(int))
return Enum.ToObject(underlyingTargetType, value);
if (underlyingSourceType == typeof(double))
return Enum.ToObject(underlyingTargetType, Convert.ChangeType(value, typeof(int), CultureInfo.InvariantCulture));
}
if (value is string s)
{
if (string.IsNullOrWhiteSpace(s))
return null;
if (underlyingTargetType == typeof(Type))
return Type.GetType(s);
// Perhaps it's a bit of a leap, but if the input is a string and the target type is IEnumerable<string>, then let's assume the string is a comma-separated list of strings.
if (typeof(IEnumerable<string>).IsAssignableFrom(underlyingTargetType))
return new[] { s };
}
if (value is IEnumerable enumerable)
{
if (underlyingTargetType is { IsGenericType: true })
{
var desiredCollectionItemType = targetType.GenericTypeArguments[0];
var desiredCollectionType = typeof(ICollection<>).MakeGenericType(desiredCollectionItemType);
if (underlyingTargetType.IsAssignableFrom(desiredCollectionType) || desiredCollectionType.IsAssignableFrom(underlyingTargetType))
{
var collectionType = typeof(List<>).MakeGenericType(desiredCollectionItemType);
var collection = (IList)Activator.CreateInstance(collectionType)!;
foreach (var item in enumerable)
{
var convertedItem = ConvertTo(item, desiredCollectionItemType);
collection.Add(convertedItem);
}
return collection;
}
}
}
try
{
return Convert.ChangeType(value, underlyingTargetType, CultureInfo.InvariantCulture);
}
catch (InvalidCastException)
{
throw new Exception($"Failed to convert an object of type {sourceType} to {underlyingTargetType}");
}
}
/// <summary>
/// Returns true if the specified type is date-like type, false otherwise.
/// </summary>
private static bool IsDateType(Type type)
{
var dateTypes = new[]
{
typeof(DateTime),
typeof(DateTimeOffset)
};
return dateTypes.Contains(type);
}
/// <summary>
/// Converts any date type to the specified target type.
/// </summary>
/// <param name="value">Any of <see cref="DateTime"/> or <see cref="DateTimeOffset"/>.</param>
/// <param name="targetType">Any of <see cref="DateTime"/> or <see cref="DateTimeOffset"/>.</param>
/// <returns>The converted value.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="value"/> is not of type <see cref="DateTime"/> or <see cref="DateTimeOffset"/>.</exception>
private static object ConvertAnyDateType(object value, Type targetType)
{
return targetType switch
{
{ } t when t == typeof(DateTime) => value switch
{
DateTime dateTime => dateTime,
DateTimeOffset dateTimeOffset => dateTimeOffset.DateTime,
_ => throw new ArgumentException("Invalid value type.")
},
{ } t when t == typeof(DateTimeOffset) => value switch
{
DateTime dateTime => new DateTimeOffset(dateTime),
DateTimeOffset dateTimeOffset => dateTimeOffset,
_ => throw new ArgumentException("Invalid value type.")
},
_ => throw new ArgumentException("Invalid target type.")
};
}
}
@@ -0,0 +1,45 @@
using System.Text.Json;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Provides extension methods for the PropertyBag class.
/// </summary>
public static class PropertyBagExtensions
{
/// <summary>
/// Tries to retrieve a value from the PropertyBag based on the provided key.
/// If the specified key does not exist in the PropertyBag, the method will return the default value obtained from the defaultValue function.
/// </summary>
/// <typeparam name="T">The type of the value to retrieve.</typeparam>
/// <param name="propertyBag">The PropertyBag to retrieve the value from.</param>
/// <param name="key">The key of the value to retrieve.</param>
/// <param name="defaultValue">A function that returns the default value to be returned if the key does not exist in the PropertyBag.</param>
/// <returns>The value associated with the key, or the default value if the key does not exist.</returns>
public static T? TryGetValueOrDefault<T>(this PropertyBag propertyBag, string key, Func<T> defaultValue)
{
if (!propertyBag.TryGetValue(key, out var value))
return defaultValue();
var json = value.ToString();
if (string.IsNullOrWhiteSpace(json))
return defaultValue();
return JsonSerializer.Deserialize<T>(json);
}
/// <summary>
/// Sets a value in the PropertyBag based on the provided key.
/// The value is serialized using JSON.
/// </summary>
/// <param name="propertyBag">The PropertyBag to set the value in.</param>
/// <param name="key">The key to associate with the value.</param>
/// <param name="value">The value to store in the PropertyBag.</param>
public static void SetValue(this PropertyBag propertyBag, string key, object value)
{
var json = JsonSerializer.Serialize(value);
propertyBag[key] = json;
}
}
@@ -0,0 +1,16 @@
using System.Text.Json.Nodes;
namespace Elsa.Api.Client.Extensions;
public static class ResilienceStrategyExtensions
{
public static string GetResilienceStrategyId(this JsonObject strategyJsonObject)
{
return strategyJsonObject.GetProperty<string>("id")!;
}
public static string GetResilienceStrategyDisplayName(this JsonObject strategyJsonObject)
{
return strategyJsonObject.GetProperty<string>("displayName")!;
}
}
@@ -0,0 +1,98 @@
using System.Collections.Concurrent;
namespace Elsa.Api.Client.Extensions;
/// <summary>
/// Adds extension methods to <see cref="Type"/>.
/// </summary>
public static class TypeExtensions
{
private static readonly ConcurrentDictionary<Type, string> SimpleAssemblyQualifiedTypeNameCache = new();
/// <summary>
/// Gets the assembly-qualified name of the type, without any version info etc.
/// E.g. "System.String, System.Private.CoreLib"
/// </summary>
public static string GetSimpleAssemblyQualifiedName(this Type type)
{
if (type is null) throw new ArgumentNullException(nameof(type));
return SimpleAssemblyQualifiedTypeNameCache.GetOrAdd(type, BuildSimplifiedName);
}
/// <summary>
/// Returns the default value for the specified type.
/// </summary>
public static object? GetDefaultValue(this Type type) => type.IsClass ? null : Activator.CreateInstance(type);
/// <summary>
/// Returns the element type of the specified type representing an array or generic enumerable.
/// </summary>
public static Type GetEnumerableElementType(this Type type)
{
if (type.IsArray)
return type.GetElementType()!;
var elementType = FindIEnumerable(type);
return elementType == null ? type : elementType.GetGenericArguments()[0];
}
/// <summary>
/// Searches for the first implemented IEnumerable interface in the given type hierarchy, and returns the generic type argument of the interface.
/// </summary>
/// <param name="sequenceType">The type to search for the IEnumerable interface.</param>
/// <returns>The generic type argument of the first implemented IEnumerable interface found in the type hierarchy, or null if none is found.</returns>
private static Type? FindIEnumerable(Type? sequenceType)
{
if (sequenceType == null || sequenceType == typeof(string))
return null;
if (sequenceType.IsArray)
return typeof(IEnumerable<>).MakeGenericType(sequenceType.GetElementType()!);
if (sequenceType.IsGenericType)
{
foreach (var arg in sequenceType.GetGenericArguments())
{
var enumerable = typeof(IEnumerable<>).MakeGenericType(arg);
if (enumerable.IsAssignableFrom(sequenceType))
return enumerable;
}
}
var interfaces = sequenceType.GetInterfaces();
if (interfaces is { Length: > 0 })
{
foreach (var interfaceType in interfaces)
{
var enumerable = FindIEnumerable(interfaceType);
if (enumerable != null) return enumerable;
}
}
if (sequenceType.BaseType != null && sequenceType.BaseType != typeof(object))
return FindIEnumerable(sequenceType.BaseType);
return null;
}
private static string BuildSimplifiedName(Type type)
{
var assemblyName = type.Assembly.GetName().Name;
if (type.IsGenericType)
{
var genericTypeName = type.GetGenericTypeDefinition().FullName!;
var backtickIndex = genericTypeName.IndexOf('`');
var typeNameWithoutArity = genericTypeName[..backtickIndex];
var arity = genericTypeName[backtickIndex..];
var genericArguments = type.GetGenericArguments();
var simplifiedGenericArguments = genericArguments.Select(BuildSimplifiedName);
return $"{typeNameWithoutArity}{arity}[[{string.Join("],[", simplifiedGenericArguments)}]], {assemblyName}";
}
var typeName = type.FullName;
return $"{typeName}, {assemblyName}";
}
}
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait />
</Weavers>
@@ -0,0 +1,44 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Elsa.Api.Client.Converters;
using Refit;
namespace Elsa.Api.Client;
/// <summary>
/// Contains helper methods for configuring Refit settings.
/// </summary>
public static class RefitSettingsHelper
{
/// <summary>
/// Creates a <see cref="RefitSettings"/> instance configured for Elsa.
/// </summary>
public static RefitSettings CreateRefitSettings(IServiceProvider serviceProvider, Action<IServiceProvider, JsonSerializerOptions>? configureJsonSerializerOptions = null)
{
var settings = new RefitSettings
{
ContentSerializer = new SystemTextJsonContentSerializer(CreateJsonSerializerOptions(serviceProvider, configureJsonSerializerOptions))
};
return settings;
}
/// <summary>
/// Creates a <see cref="JsonSerializerOptions"/> instance configured for Elsa.
/// </summary>
public static JsonSerializerOptions CreateJsonSerializerOptions(IServiceProvider serviceProvider, Action<IServiceProvider, JsonSerializerOptions>? configureJsonSerializerOptions = null)
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new VersionOptionsJsonConverter());
options.Converters.Add(new TypeJsonConverter());
configureJsonSerializerOptions?.Invoke(serviceProvider, options);
return options;
}
}
@@ -0,0 +1,30 @@
using System.Net.Http.Headers;
using Elsa.Api.Client.Options;
using Microsoft.Extensions.Options;
namespace Elsa.Api.Client.HttpMessageHandlers;
/// <summary>
/// An <see cref="HttpMessageHandler"/> that configures the outgoing HTTP request to use an API key as the authorization header.
/// </summary>
public class ApiKeyHttpMessageHandler : DelegatingHandler
{
private readonly ElsaClientOptions _options;
/// <summary>
/// Initializes a new instance of the <see cref="ApiKeyHttpMessageHandler"/> class.
/// </summary>
public ApiKeyHttpMessageHandler(IOptions<ElsaClientOptions> options)
{
_options = options.Value;
}
/// <inheritdoc />
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var apiKey = _options.ApiKey;
request.Headers.Authorization = new AuthenticationHeaderValue("ApiKey", apiKey);
return await base.SendAsync(request, cancellationToken);
}
}
@@ -0,0 +1,7 @@
using System.ComponentModel;
// ReSharper disable once CheckNamespace
namespace System.Runtime.CompilerServices;
[EditorBrowsable(EditorBrowsableState.Never)]
internal sealed class IsExternalInit;
@@ -0,0 +1,48 @@
using System.Text.Json;
using Elsa.Api.Client.HttpMessageHandlers;
using Microsoft.Extensions.DependencyInjection;
using Polly;
namespace Elsa.Api.Client.Options;
/// <summary>
/// Represents options for building an Elsa API client.
/// </summary>
public class ElsaClientBuilderOptions
{
/// <summary>
/// Gets or sets the base address of the Elsa server.
/// </summary>
public Uri BaseAddress { get; set; } = default!;
/// <summary>
/// Gets or sets the API key function to use when authenticating with the Elsa server.
/// </summary>
public string? ApiKey { get; set; }
/// <summary>
/// A <see cref="DelegatingHandler"/> type that can be used to authenticate with the Elsa server.
/// Defaults to <see cref="ApiKeyHttpMessageHandler"/>.
/// </summary>
public Type AuthenticationHandler { get; set; } = typeof(ApiKeyHttpMessageHandler);
/// <summary>
/// Gets or sets a delegate that can be used to configure the HTTP client.
/// </summary>
public Action<IServiceProvider, HttpClient>? ConfigureHttpClient { get; set; }
/// <summary>
/// Gets or sets a delegate that can be used to configure the HTTP client builder.
/// </summary>
public Action<IHttpClientBuilder> ConfigureHttpClientBuilder { get; set; } = _ => { };
/// <summary>
/// Gets or sets a delegate that can be used to configure the retry policy.
/// </summary>
public Action<IHttpClientBuilder>? ConfigureRetryPolicy { get; set; } = builder => builder.AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))));
/// <summary>
/// Gets or sets a delegate that can be used to configure the JSON serializer options.
/// </summary>
public Action<IServiceProvider, JsonSerializerOptions>? ConfigureJsonSerializerOptions { get; set; }
}
@@ -0,0 +1,22 @@
namespace Elsa.Api.Client.Options;
/// <summary>
/// Represents options for the Elsa client.
/// </summary>
public class ElsaClientOptions
{
/// <summary>
/// Gets or sets the base address of the Elsa server.
/// </summary>
public Uri BaseAddress { get; set; } = default!;
/// <summary>
/// Gets or sets the API key function to use when authenticating with the Elsa server.
/// </summary>
public string? ApiKey { get; set; }
/// <summary>
/// Gets or sets a delegate that can be used to configure the HTTP client.
/// </summary>
public Action<IServiceProvider, HttpClient>? ConfigureHttpClient { get; set; }
}
@@ -0,0 +1,10 @@
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
namespace Elsa.Api.Client.RealTime.Messages;
/// <summary>
/// Contains information about the activity executed event.
/// </summary>
/// <param name="ActivityId">The ID of the activity.</param>
/// <param name="Stats">Execution stats about the activity.</param>
public record ActivityExecutedMessage(string ActivityId, ActivityExecutionStats Stats);
@@ -0,0 +1,10 @@
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
namespace Elsa.Api.Client.RealTime.Messages;
/// <summary>
/// Contains information about the activity executing event.
/// </summary>
/// <param name="ActivityId">The ID of the activity.</param>
/// <param name="Stats">Execution stats about the activity.</param>
public record ActivityExecutingMessage(string ActivityId, ActivityExecutionStats Stats);
@@ -0,0 +1,9 @@
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
namespace Elsa.Api.Client.RealTime.Messages;
/// <summary>
/// Contains information about the activity execution logs updated event.
/// </summary>
/// <param name="Stats">Execution stats about a set of activities.</param>
public record ActivityExecutionLogUpdatedMessage(ICollection<ActivityExecutionStats> Stats);
@@ -0,0 +1,6 @@
namespace Elsa.Api.Client.RealTime.Messages;
/// <summary>
/// Represents a message that is sent when the workflow execution log is updated.
/// </summary>
public record WorkflowExecutionLogUpdatedMessage();
@@ -0,0 +1,7 @@
namespace Elsa.Api.Client.RealTime.Messages;
/// <summary>
/// Represents a message that is sent when the workflow instance is updated.
/// </summary>
/// <param name="WorkflowInstanceId">The ID of the workflow instance that was updated.</param>
public record WorkflowInstanceUpdatedMessage(string WorkflowInstanceId);
@@ -0,0 +1,22 @@
using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Requests;
using Elsa.Api.Client.Resources.ActivityDescriptorOptions.Responses;
using Refit;
namespace Elsa.Api.Client.Resources.ActivityDescriptorOptions.Contracts;
/// <summary>
/// Represents a client for the workflow definitions API.
/// </summary>
public interface IActivityDescriptorOptionsApi
{
/// <summary>
/// Lists activity descriptors options.
/// </summary>
/// <param name="activityTypeName">The TypeName of the activity </param>
/// <param name="propertyName">The name of the property</param>
/// <param name="request">The context request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The response containing the activity descriptors.</returns>
[Post("/descriptors/activities/{activityTypeName}/options/{propertyName}")]
Task<GetActivityDescriptorOptionsResponse> GetAsync(string activityTypeName, string propertyName, [Body]GetActivityDescriptorOptionsRequest request, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,12 @@
namespace Elsa.Api.Client.Resources.ActivityDescriptorOptions.Requests;
/// <summary>
/// Represents a request to Get Activity Descriptor Options.
/// </summary>
public class GetActivityDescriptorOptionsRequest
{
/// <summary>
/// Object context use to pass custom information
/// </summary>
public object? Context { get; set; }
}
@@ -0,0 +1,6 @@
namespace Elsa.Api.Client.Resources.ActivityDescriptorOptions.Responses;
/// <summary>
/// Represents a response from get activity descriptors Options.
/// </summary>
/// <param name="Items">The options elements.</param>
public record GetActivityDescriptorOptionsResponse(IDictionary<string,object> Items);
@@ -0,0 +1,20 @@
using Elsa.Api.Client.Resources.ActivityDescriptors.Requests;
using Elsa.Api.Client.Resources.ActivityDescriptors.Responses;
using Refit;
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Contracts;
/// <summary>
/// Represents a client for the workflow definitions API.
/// </summary>
public interface IActivityDescriptorsApi
{
/// <summary>
/// Lists activity descriptors.
/// </summary>
/// <param name="request">The request containing options for listing activity descriptors.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The response containing the activity descriptors.</returns>
[Get("/descriptors/activities")]
Task<ListActivityDescriptorsResponse> ListAsync([Query]ListActivityDescriptorsRequest request, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,27 @@
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Enums;
/// <summary>
/// Represents the kind of an activity, which affects how it is executed.
/// </summary>
public enum ActivityKind
{
/// <summary>
/// Always run synchronously.
/// </summary>
Action,
/// <summary>
/// Can be used to trigger new workflows.
/// </summary>
Trigger,
/// <summary>
/// Always run asynchronously.
/// </summary>
Job,
/// <summary>
/// Run synchronously by default (like <see cref="Action"/>, but can be configured to run asynchronously (like <see cref="Job"/>).
/// </summary>
Task
}
@@ -0,0 +1,17 @@
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Enums;
/// <summary>
/// The type of a port.
/// </summary>
public enum PortType
{
/// <summary>
/// A port that is embedded in the activity.
/// </summary>
Embedded,
/// <summary>
/// A flow port.
/// </summary>
Flow
}
@@ -0,0 +1,94 @@
using Elsa.Api.Client.Resources.ActivityDescriptors.Enums;
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
/// <summary>
/// A descriptor of an activity type.
/// </summary>
public record ActivityDescriptor
{
/// <summary>
/// The fully qualified name of the activity type.
/// </summary>
public string TypeName { get; init; } = default!;
/// <summary>
/// The namespace of the activity type.
/// </summary>
public string Namespace { get; init; } = default!;
/// <summary>
/// The name of the activity type.
/// </summary>
public string Name { get; init; } = default!;
/// <summary>
/// The version of the activity type.
/// </summary>
public int Version { get; init; }
/// <summary>
/// The category of the activity type.
/// </summary>
public string Category { get; init; } = default!;
/// <summary>
/// The display name of the activity type.
/// </summary>
public string? DisplayName { get; init; }
/// <summary>
/// The description of the activity type.
/// </summary>
public string? Description { get; init; }
/// <summary>
/// The input properties of the activity type.
/// </summary>
public IReadOnlyCollection<InputDescriptor> Inputs { get; init; } = new List<InputDescriptor>();
/// <summary>
/// The output properties of the activity type.
/// </summary>
public IReadOnlyCollection<OutputDescriptor> Outputs { get; init; } = new List<OutputDescriptor>();
/// <summary>
/// The kind of activity.
/// </summary>
public ActivityKind Kind { get; set; } = ActivityKind.Action;
/// <summary>
/// The ports of the activity type.
/// </summary>
public IReadOnlyCollection<Port> Ports { get; init; } = new List<Port>();
/// <summary>
/// The custom properties of the activity type.
/// </summary>
public IReadOnlyDictionary<string, object> CustomProperties { get; set; } = new Dictionary<string, object>();
/// <summary>
/// The properties to set when constructing an activity in the designer.
/// </summary>
public IDictionary<string, object> ConstructionProperties { get; set; } = new Dictionary<string, object>();
/// <summary>
/// A value indicating whether this activity is a container of child activities.
/// </summary>
public bool IsContainer { get; set; }
/// <summary>
/// Whether this activity type is selectable from activity pickers.
/// </summary>
public bool IsBrowsable { get; set; }
/// <summary>
/// Whether this activity type is a start activity.
/// </summary>
public bool IsStart { get; set; }
/// <summary>
/// Whether this activity type is a terminal activity.
/// </summary>
public bool IsTerminal { get; set; }
}
@@ -0,0 +1,56 @@
using JetBrains.Annotations;
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
/// <summary>
/// A descriptor of an activity's input property.
/// </summary>
[PublicAPI]
public class InputDescriptor : PropertyDescriptor
{
/// <inheritdoc />
public InputDescriptor()
{
}
/// <summary>
/// True if the property is wrapped with an <c>"Input{T}"</c> type, false otherwise.
/// </summary>
public bool IsWrapped { get; set; }
/// <summary>
/// A string value that hints at what UI control might be used to render in a UI tool.
/// </summary>
public string UIHint { get; set; } = default!;
/// <summary>
/// The category to which this input belongs. Can be used by UI to e.g. render different inputs in different tabs.
/// </summary>
public string? Category { get; set; }
/// <summary>
/// The default value.
/// </summary>
public object? DefaultValue { get; set; }
/// <summary>
/// The default syntax.
/// </summary>
public string? DefaultSyntax { get; set; }
/// <summary>
/// True if the input is readonly, false otherwise.
/// </summary>
public bool? IsReadOnly { get; set; }
/// <summary>
/// The storage driver type to use for persistence.
/// If no driver is specified, the referenced memory block will remain in memory for as long as the expression execution context exists.
/// </summary>
public string? StorageDriverType { get; set; }
/// <summary>
/// A dictionary of UI specifications to be used by the UI.
/// </summary>
public IDictionary<string, object>? UISpecifications { get; set; }
}
@@ -0,0 +1,11 @@
using JetBrains.Annotations;
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
/// <summary>
/// A descriptor of an activity's output property.
/// </summary>
[PublicAPI]
public class OutputDescriptor : PropertyDescriptor
{
}
@@ -0,0 +1,29 @@
using Elsa.Api.Client.Resources.ActivityDescriptors.Enums;
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
/// <summary>
/// Represents a port on an activity.
/// </summary>
public class Port
{
/// <summary>
/// Gets or sets the name of the port.
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// Gets or sets the display name of the port.
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// Gets or sets the type of the port.
/// </summary>
public PortType Type { get; set; }
/// <summary>
/// Gets or sets the visibility of the port.
/// </summary>
public bool? IsBrowsable { get; set; } = true;
}
@@ -0,0 +1,47 @@
using System.Text.Json.Serialization;
using JetBrains.Annotations;
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Models;
/// <summary>
/// A base type for <see cref="InputDescriptor"/> and <see cref="OutputDescriptor"/>.
/// </summary>
[PublicAPI]
public abstract class PropertyDescriptor
{
/// <summary>
/// The name.
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// The .NET type.
/// </summary>
[JsonPropertyName("typeName")]
public string TypeName { get; set; } = default!;
/// <summary>
/// The user friendly name of the input. Used by UI tools.
/// </summary>
public string? DisplayName { get; set; }
/// <summary>
/// The user friendly description of the input. Used by UI tools.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// The order in which this input should be displayed by UI tools.
/// </summary>
public float Order { get; set; }
/// <summary>
/// True if this input should be displayed by UI tools, false otherwise.
/// </summary>
public bool? IsBrowsable { get; set; }
/// <summary>
/// True if this input property is synthetic, which means it does not exist physically on the activity's .NET type.
/// </summary>
public bool IsSynthetic { get; set; }
}
@@ -0,0 +1,14 @@
using Refit;
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Requests;
/// <summary>
/// Represents a request to list activity descriptors.
/// </summary>
public class ListActivityDescriptorsRequest
{
/// <summary>
/// Whether to refresh the activity descriptors or not.
/// </summary>
[Query] public bool Refresh { get; set; }
}
@@ -0,0 +1,10 @@
using Elsa.Api.Client.Resources.ActivityDescriptors.Models;
namespace Elsa.Api.Client.Resources.ActivityDescriptors.Responses;
/// <summary>
/// Represents a response from listing activity descriptors.
/// </summary>
/// <param name="Items">The activity descriptors.</param>
/// <param name="Count">The total number of activity descriptors.</param>
public record ListActivityDescriptorsResponse(ICollection<ActivityDescriptor> Items, int Count);
@@ -0,0 +1,48 @@
using Elsa.Api.Client.Resources.ActivityExecutions.Models;
using Elsa.Api.Client.Resources.ActivityExecutions.Requests;
using Elsa.Api.Client.Shared.Models;
using Refit;
namespace Elsa.Api.Client.Resources.ActivityExecutions.Contracts;
/// <summary>
/// Represents a client for the activity executions API.
/// </summary>
public interface IActivityExecutionsApi
{
/// <summary>
/// Gets a report of activity executions for a given workflow instance.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The response containing the activity execution report.</returns>
[Post("/activity-executions/report")]
Task<ActivityExecutionReport> GetReportAsync([Body] GetActivityExecutionReportRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// Lists activity executions for a given activity in a workflow instance.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The response containing a list of activity executions.</returns>
[Get("/activity-executions/list")]
Task<ListResponse<ActivityExecutionRecord>> ListAsync(ListActivityExecutionsRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// Lists activity execution summaries for a given activity in a workflow instance.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The response containing a list of activity execution summaries.</returns>
[Get("/activity-execution-summaries/list")]
Task<ListResponse<ActivityExecutionRecordSummary>> ListSummariesAsync(ListActivityExecutionsRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// Gets a single activity execution by ID.
/// </summary>
/// <param name="id">The ID of the activity execution.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The activity execution.</returns>
[Get("/activity-executions/{id}")]
Task<ActivityExecutionRecord?> GetAsync(string id, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,93 @@
using Elsa.Api.Client.Resources.WorkflowInstances.Models;
using Elsa.Api.Client.Shared.Models;
using JetBrains.Annotations;
namespace Elsa.Api.Client.Resources.ActivityExecutions.Models;
/// <summary>
/// Represents a single workflow execution, associated with an individual activity instance.
/// </summary>
[UsedImplicitly]
public class ActivityExecutionRecord : Entity
{
/// <summary>
/// Gets or sets the workflow instance ID.
/// </summary>
public string WorkflowInstanceId { get; set; } = null!;
/// <summary>
/// Gets or sets the activity ID.
/// </summary>
public string ActivityId { get; set; } = null!;
/// <summary>
/// Gets or sets the activity node ID.
/// </summary>
public string ActivityNodeId { get; set; } = null!;
/// <summary>
/// The type of the activity.
/// </summary>
public string ActivityType { get; set; } = null!;
/// <summary>
/// The version of the activity type.
/// </summary>
public int ActivityTypeVersion { get; set; }
/// <summary>
/// The name of the activity.
/// </summary>
public string? ActivityName { get; set; } = null!;
/// <summary>
/// The state of the activity at the time this record is created or last updated.
/// </summary>
public IDictionary<string, object?>? ActivityState { get; set; }
/// <summary>
/// Any additional payload associated with the log record.
/// </summary>
public IDictionary<string, object?>? Payload { get; set; }
/// <summary>
/// Any outputs provided by the activity.
/// </summary>
public IDictionary<string, object?>? Outputs { get; set; }
/// <summary>
/// Any properties provided by the activity.
/// </summary>
public IDictionary<string, object?> Properties { get; set; } = new Dictionary<string, object?>();
/// <summary>
/// Any metadata provided by the activity. In contrast to the <see cref="Properties"/> property, this metadata is also persisted as part of the activity execution summary record.
/// It is therefore not suitable for larger payloads.
/// </summary>
public IDictionary<string, object?> Metadata { get; set; } = new Dictionary<string, object?>();
/// <summary>
/// Gets or sets the exception that occurred during the activity execution.
/// </summary>
public ExceptionState? Exception { get; set; }
/// <summary>
/// Gets or sets the time at which the activity execution began.
/// </summary>
public DateTimeOffset StartedAt { get; set; } = default!;
/// <summary>
/// Gets or sets whether the activity has any bookmarks.
/// </summary>
public bool HasBookmarks { get; set; }
/// <summary>
/// Gets or sets the status of the activity.
/// </summary>
public ActivityStatus Status { get; set; }
/// <summary>
/// Gets or sets the time at which the activity execution completed.
/// </summary>
public DateTimeOffset? CompletedAt { get; set; }
}
@@ -0,0 +1,86 @@
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Resources.ActivityExecutions.Models;
/// <summary>
/// Represents a summarized view of a single activity execution of an activity instance.
/// </summary>
public class ActivityExecutionRecordSummary : Entity
{
/// <summary>
/// Gets or sets the workflow instance ID.
/// </summary>
public string WorkflowInstanceId { get; set; } = null!;
/// <summary>
/// Gets or sets the activity ID.
/// </summary>
public string ActivityId { get; set; } = null!;
/// <summary>
/// Gets or sets the activity node ID.
/// </summary>
public string ActivityNodeId { get; set; } = null!;
/// <summary>
/// The type of the activity.
/// </summary>
public string ActivityType { get; set; } = null!;
/// <summary>
/// The version of the activity type.
/// </summary>
public int ActivityTypeVersion { get; set; }
/// <summary>
/// The name of the activity.
/// </summary>
public string? ActivityName { get; set; }
/// <summary>
/// Gets or sets the time at which the activity execution began.
/// </summary>
public DateTimeOffset StartedAt { get; set; }
/// <summary>
/// Gets or sets whether the activity has any bookmarks.
/// </summary>
public bool HasBookmarks { get; set; }
/// <summary>
/// Gets or sets metadata for the activity execution.
/// </summary>
public IDictionary<string, object?>? Metadata { get; set; }
/// <summary>
/// Gets or sets the status of the activity.
/// </summary>
public ActivityStatus Status { get; set; }
/// <summary>
/// Gets or sets the time at which the activity execution completed.
/// </summary>
public DateTimeOffset? CompletedAt { get; set; }
/// <summary>
/// Returns a summary view of the specified <see cref="ActivityExecutionRecord"/>.
/// </summary>
public static ActivityExecutionRecordSummary FromRecord(ActivityExecutionRecord record)
{
return new()
{
Id = record.Id,
WorkflowInstanceId = record.WorkflowInstanceId,
ActivityId = record.ActivityId,
ActivityNodeId = record.ActivityNodeId,
ActivityType = record.ActivityType,
ActivityTypeVersion = record.ActivityTypeVersion,
ActivityName = record.ActivityName,
StartedAt = record.StartedAt,
HasBookmarks = record.HasBookmarks,
Status = record.Status,
CompletedAt = record.CompletedAt,
Metadata = record.Metadata
};
}
}
@@ -0,0 +1,7 @@
namespace Elsa.Api.Client.Resources.ActivityExecutions.Models;
/// <summary>
/// Represents a report of activity executions.
/// </summary>
/// <param name="Stats">The activity execution stats.</param>
public record ActivityExecutionReport(ICollection<ActivityExecutionStats> Stats);
@@ -0,0 +1,47 @@
namespace Elsa.Api.Client.Resources.ActivityExecutions.Models;
/// <summary>
/// Represents statistics about the execution of an activity.
/// </summary>
public class ActivityExecutionStats
{
/// <summary>
/// Gets or sets the ID of the activity.
/// </summary>
public string ActivityId { get; set; } = null!;
/// <summary>
/// Gets or sets the node ID of the activity.
/// </summary>
public string ActivityNodeId { get; set; } = null!;
/// <summary>
/// Gets or sets the number of uncompleted executions.
/// </summary>
public long StartedCount { get; set; }
/// <summary>
/// Gets or sets the number of completed executions.
/// </summary>
public long CompletedCount { get; set; }
/// <summary>
/// Gets or sets the number of uncompleted executions.
/// </summary>
public long UncompletedCount { get; set; }
/// <summary>
/// Gets or sets a value whether this activity is waiting for bookmarks to be resumed.
/// </summary>
public bool IsBlocked { get; set; }
/// <summary>
/// Gets or sets a value whether this activity is faulted.
/// </summary>
public bool IsFaulted { get; set; }
/// <summary>
/// Gets or sets additional metadata associated with the activity execution.
/// </summary>
public IDictionary<string, object>? Metadata { get; set; }
}
@@ -0,0 +1,17 @@
namespace Elsa.Api.Client.Resources.ActivityExecutions.Requests;
/// <summary>
/// Represents a request for a list of activity execution records for a given activity in a workflow instance.
/// </summary>
public class GetActivityExecutionReportRequest
{
/// <summary>
/// The ID of the workflow instance to get the execution log for.
/// </summary>
public string WorkflowInstanceId { get; set; } = default!;
/// <summary>
/// The node IDs of the activities to get the execution record for.
/// </summary>
public ICollection<string> ActivityNodeIds { get; set; } = default!;
}
@@ -0,0 +1,21 @@
using Refit;
namespace Elsa.Api.Client.Resources.ActivityExecutions.Requests;
/// <summary>
/// Represents a request for a list of activity execution records for a given activity in a workflow instance.
/// </summary>
public class ListActivityExecutionsRequest
{
/// <summary>
/// The ID of the workflow instance to get the execution log for.
/// </summary>
[Query]
public string WorkflowInstanceId { get; set; } = default!;
/// <summary>
/// The node ID of the activity to get the execution record for.
/// </summary>
[Query]
public string ActivityNodeId { get; set; } = default!;
}
@@ -0,0 +1,52 @@
using Elsa.Api.Client.Resources.Alterations.Models;
using Elsa.Api.Client.Resources.Alterations.Requests;
using Elsa.Api.Client.Resources.Alterations.Responses;
using Refit;
namespace Elsa.Api.Client.Resources.Alterations.Contracts;
/// <summary>
/// Represents a client for the alterations API. Requires the Elsa.Alterations feature.
/// </summary>
public interface IAlterationsApi
{
/// <summary>
/// Returns an alteration plan and its associated jobs.
/// </summary>
/// <param name="id">The ID of the alteration plan to return.</param>
/// <param name="cancellationToken">The cancellation token.</param>
[Get("/alterations/{id}")]
Task<GetAlterationPlanResponse> GetAsync(string id, CancellationToken cancellationToken = default);
/// <summary>
/// Determines which workflow instances a "Submit" request would target without actually running an alteration
/// </summary>
/// <param name="request">The requested workflow filter to dry run</param>
/// <param name="cancellationToken">The cancellation token.</param>
[Post("/alterations/dry-run")]
Task<DryRunResponse> DryRun(AlterationWorkflowInstanceFilter request, CancellationToken cancellationToken = default);
/// <summary>
/// Submits an alteration plan and a filter for workflows instances to be executed against
/// </summary>
/// <param name="request">The alterations and filter to submit</param>
/// <param name="cancellationToken">The cancellation token.</param>
[Post("/alterations/submit")]
Task<SubmitResponse> Submit(AlterationPlanParams request, CancellationToken cancellationToken = default);
/// <summary>
/// Runs an alteration plan and a list of workflow Instance Ids to be executed against
/// </summary>
/// <param name="request">The alterations and workflowInstanceIds to execute</param>
/// <param name="cancellationToken">The cancellation token.</param>
[Post("/alterations/run")]
Task<RunResponse> Run(RunRequest request, CancellationToken cancellationToken = default);
/// <summary>
/// Retries the specified workflow instances.
/// </summary>
/// <param name="request">The request containing the selection of workflow instances to retry.</param>
/// <param name="cancellationToken">The cancellation token.</param>
[Post("/alterations/workflows/retry")]
Task<BulkRetryResponse> BulkRetryAsync(BulkRetryRequest request, CancellationToken cancellationToken);
}
@@ -0,0 +1,32 @@
namespace Elsa.Api.Client.Resources.Alterations.Enums;
/// <summary>
/// Represents the status of an activity.
/// </summary>
public enum ActivityStatus
{
/// <summary>
/// The activity is in the Pending state.
/// </summary>
Pending,
/// <summary>
/// The activity is in the Running state. Note that event if an activity is running, it may not be executing.
/// </summary>
Running,
/// <summary>
/// The activity is in the Completed state.
/// </summary>
Completed,
/// <summary>
/// The activity is in the Canceled state.
/// </summary>
Canceled,
/// <summary>
/// The activity is in the Faulted state.
/// </summary>
Faulted
}
@@ -0,0 +1,27 @@
namespace Elsa.Api.Client.Resources.Alterations.Enums;
/// <summary>
/// The status of an alteration plan for a workflow instance.
/// </summary>
public enum AlterationJobStatus
{
/// <summary>
/// The plan is pending execution.
/// </summary>
Pending,
/// <summary>
/// The plan is currently being executed.
/// </summary>
Running,
/// <summary>
/// The plan has been completed.
/// </summary>
Completed,
/// <summary>
/// The job has failed.
/// </summary>
Failed
}
@@ -0,0 +1,37 @@
namespace Elsa.Api.Client.Resources.Alterations.Enums;
/// <summary>
/// The status of an alteration plan.
/// </summary>
public enum AlterationPlanStatus
{
/// <summary>
/// The plan is pending execution.
/// </summary>
Pending,
/// <summary>
/// The plan is currently generating jobs.
/// </summary>
Generating,
/// <summary>
/// The plan is currently dispatching jobs.
/// </summary>
Dispatching,
/// <summary>
/// The plan is currently being executed.
/// </summary>
Running,
/// <summary>
/// The plan has been completed.
/// </summary>
Completed,
/// <summary>
/// The plan has failed.
/// </summary>
Failed
}
@@ -0,0 +1,34 @@
using Elsa.Api.Client.Resources.Alterations.Enums;
namespace Elsa.Api.Client.Resources.Alterations.Models;
/// <summary>
/// A filter for activities within a workflow instance
/// </summary>
public class ActivityFilter
{
/// <summary>
/// The ID of the activity.
/// </summary>
public string? ActivityId { get; set; }
/// <summary>
/// The ID of the activity instance.
/// </summary>
public string? ActivityInstanceId { get; set; }
/// <summary>
/// The node ID of the activity.
/// </summary>
public string? NodeId { get; set; }
/// <summary>
/// The name of the activity.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// The status of the activity.
/// </summary>
public ActivityStatus? Status { get; set; }
}
@@ -0,0 +1,45 @@
using Elsa.Api.Client.Resources.Alterations.Enums;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Resources.Alterations.Models;
/// <summary>
/// Represents the execution of the plan for an individual workflow instance.
/// </summary>
public class AlterationJob : Entity
{
/// <summary>
/// The ID of the plan that this job belongs to.
/// </summary>
public string PlanId { get; set; } = default!;
/// <summary>
/// The ID of the workflow instance that this job applies to.
/// </summary>
public string WorkflowInstanceId { get; set; } = default!;
/// <summary>
/// The status of the job.
/// </summary>
public AlterationJobStatus Status { get; set; }
/// <summary>
/// The serialized log of the job.
/// </summary>
public ICollection<AlterationLogEntry>? Log { get; set; } = new List<AlterationLogEntry>();
/// <summary>
/// The date and time at which the job was created.
/// </summary>
public DateTimeOffset CreatedAt { get; set; }
/// <summary>
/// The date and time at which the job was started.
/// </summary>
public DateTimeOffset? StartedAt { get; set; }
/// <summary>
/// The date and time at which the job was completed.
/// </summary>
public DateTimeOffset? CompletedAt { get; set; }
}
@@ -0,0 +1,13 @@
namespace Elsa.Api.Client.Resources.Alterations.Models;
/// <summary>
/// Represents a log of alterations.
/// </summary>
public class AlterationLog
{
/// <summary>
/// The log entries.
/// </summary>
public ICollection<AlterationLogEntry> LogEntries { get; set; } = new List<AlterationLogEntry>();
}
@@ -0,0 +1,12 @@
using Microsoft.Extensions.Logging;
namespace Elsa.Api.Client.Resources.Alterations.Models;
/// <summary>
/// An individual log entry about an alteration
/// </summary>
/// <param name="Message"></param>
/// <param name="LogLevel"></param>
/// <param name="Timestamp"></param>
/// <param name="EventName"></param>
public record AlterationLogEntry(string Message, LogLevel LogLevel, DateTimeOffset Timestamp, string? EventName = null);
@@ -0,0 +1,41 @@
using System.Text.Json.Nodes;
using Elsa.Api.Client.Resources.Alterations.Enums;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Resources.Alterations.Models;
/// <summary>
/// A plan that contains a list of alterations to be applied to a set of workflow instances.
/// </summary>
public class AlterationPlan : Entity
{
/// <summary>
/// The alterations to be applied.
/// </summary>
public ICollection<JsonObject> Alterations { get; set; } = new List<JsonObject>();
/// <summary>
/// The IDs of the workflow instances that this plan applies to.
/// </summary>
public AlterationWorkflowInstanceFilter WorkflowInstanceFilter { get; set; } = new();
/// <summary>
/// The status of the plan.
/// </summary>
public AlterationPlanStatus Status { get; set; }
/// <summary>
/// The date and time at which the plan was created.
/// </summary>
public DateTimeOffset CreatedAt { get; set; }
/// <summary>
/// The date and time at which the plan was started.
/// </summary>
public DateTimeOffset? StartedAt { get; set; }
/// <summary>
/// The date and time at which the plan was completed.
/// </summary>
public DateTimeOffset? CompletedAt { get; set; }
}
@@ -0,0 +1,24 @@
using System.Text.Json.Nodes;
namespace Elsa.Api.Client.Resources.Alterations.Models;
/// <summary>
/// Represents the execution of an alteration plan against a set of workflow instances defined by the given filter
/// </summary>
public class AlterationPlanParams
{
/// <summary>
/// The unique identifier for the alteration plan. If not specified, a new ID will be generated.
/// </summary>
public string? Id { get; set; }
/// <summary>
/// The alterations to be applied.
/// </summary>
public ICollection<JsonObject> Alterations { get; set; } = new List<JsonObject>();
/// <summary>
/// The IDs of the workflow instances that this plan applies to.
/// </summary>
public AlterationWorkflowInstanceFilter Filter { get; set; } = new();
}
@@ -0,0 +1,76 @@
using Elsa.Api.Client.Resources.WorkflowInstances.Enums;
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Resources.Alterations.Models;
/// <summary>
/// Represents a filter for workflow instances.
/// </summary>
public class AlterationWorkflowInstanceFilter
{
/// <summary>
/// If the filter is empty, all records are matched.
/// </summary>
public bool EmptyFilterSelectsAll { get; set; }
/// <summary>
/// The IDs of the workflow instances that this plan applies to.
/// </summary>
public IEnumerable<string>? WorkflowInstanceIds { get; set; }
/// <summary>
/// The correlation IDs of the workflow instances that this plan applies to.
/// </summary>
public IEnumerable<string>? CorrelationIds { get; set; }
/// <summary>
/// A collection of names associated with the workflow instances being filtered.
/// </summary>
public ICollection<string>? Names { get; set; }
/// <summary>
/// A search term used to filter workflow instances based on matching criteria.
/// </summary>
public string? SearchTerm { get; set; }
/// <summary>
/// A collection of timestamp filters used for filtering data based on specified timestamp columns and operators.
/// </summary>
public IEnumerable<TimestampFilter>? TimestampFilters { get; set; }
/// <summary>
/// The IDs of the workflow definitions that this plan applies to.
/// </summary>
public ICollection<string>? DefinitionIds { get; set; }
/// <summary>
/// The IDs of the workflow definitions that this plan applies to.
/// </summary>
public IEnumerable<string>? DefinitionVersionIds { get; set; }
/// <summary>
/// Whether the workflow instances to match have incidents.
/// </summary>
public bool? HasIncidents { get; set; }
/// <summary>
/// Whether the workflow instances to match are system workflows. Defaults to <c>false</c>.
/// </summary>
public bool? IsSystem { get; set; } = false;
/// <summary>
/// Represents the workflow statuses included in the filter.
/// </summary>
public ICollection<WorkflowStatus>? Statuses { get; set; }
/// <summary>
/// A collection of sub-statuses used to filter workflow instances by their specific sub-state.
/// </summary>
public ICollection<WorkflowSubStatus>? SubStatuses { get; set; }
/// <summary>
/// Represents a collection of filters for activities.
/// </summary>
public IEnumerable<ActivityFilter>? ActivityFilters { get; set; }
}
@@ -0,0 +1,27 @@
namespace Elsa.Api.Client.Resources.Alterations.Models;
/// <summary>
/// The result of running a series of alterations.
/// </summary>
public class RunAlterationsResult
{
/// <summary>
/// The ID of the workflow instance that was altered.
/// </summary>
public string WorkflowInstanceId { get; set; } = string.Empty;
/// <summary>
/// A log of the alterations that were run.
/// </summary>
public AlterationLog Log { get; set; } = new();
/// <summary>
/// A flag indicating whether the workflow has scheduled work.
/// </summary>
public bool WorkflowHasScheduledWork { get; set; }
/// <summary>
/// A flag indicating whether the alterations have succeeded.
/// </summary>
public bool IsSuccessful { get; set; }
}
@@ -0,0 +1,17 @@
namespace Elsa.Api.Client.Resources.Alterations.Requests;
/// <summary>
/// Represents a request to bulk retry workflow instances.
/// </summary>
public class BulkRetryRequest
{
/// <summary>
/// The IDs of the workflow instances that have incidents to be retried.
/// </summary>
public ICollection<string> WorkflowInstanceIds { get; set; } = new List<string>();
/// <summary>
/// An optional list of explicitly specified activity IDs to retry. If omitted, all faulted activities will be retried.
/// </summary>
public ICollection<string>? ActivityIds { get; set; }
}
@@ -0,0 +1,14 @@
using Elsa.Api.Client.Resources.Alterations.Models;
namespace Elsa.Api.Client.Resources.Alterations.Responses;
/// <summary>
/// Represents a response to bulk retry workflow instances.
/// </summary>
public class BulkRetryResponse
{
/// <summary>
/// The alterations that resulted from the bulk retry request
/// </summary>
public ICollection<RunAlterationsResult> Results { get;set; } = new List<RunAlterationsResult>();
}
@@ -0,0 +1,12 @@
namespace Elsa.Api.Client.Resources.Alterations.Responses;
/// <summary>
/// The response to the DryRun request
/// </summary>
public class DryRunResponse
{
/// <summary>
/// The list of workflow instance IDs that would be affected by a "Submit" request
/// </summary>
public ICollection<string> WorkflowInstanceIds { get; set; } = new List<string>();
}
@@ -0,0 +1,19 @@
using Elsa.Api.Client.Resources.Alterations.Models;
namespace Elsa.Api.Client.Resources.Alterations.Responses;
/// <summary>
/// The response from the "Get" alteration plan endpoint
/// </summary>
public class GetAlterationPlanResponse
{
/// <summary>
/// The alteration plan mathching the provided ID
/// </summary>
public AlterationPlan Plan { get; set; } = new();
/// <summary>
/// The list of jobs that exist for that AlterationPlan
/// </summary>
public ICollection<AlterationJob> Jobs { get; set; } = new List<AlterationJob>();
}
@@ -0,0 +1,19 @@
using System.Text.Json.Nodes;
namespace Elsa.Api.Client.Resources.Alterations.Responses;
/// <summary>
/// A plan that contains a list of alterations to be applied to a set of workflow instances.
/// </summary>
public class RunRequest
{
/// <summary>
/// The alterations to be applied.
/// </summary>
public ICollection<JsonObject> Alterations { get; set; } = new List<JsonObject>();
/// <summary>
/// The IDs of the workflow instances that this plan applies to.
/// </summary>
public ICollection<string> WorkflowInstanceIds { get; set; } = new List<string>();
}
@@ -0,0 +1,14 @@
using Elsa.Api.Client.Resources.Alterations.Models;
namespace Elsa.Api.Client.Resources.Alterations.Responses;
/// <summary>
/// The response to the Run endpoint
/// </summary>
public class RunResponse
{
/// <summary>
/// The alteration results of a Run request
/// </summary>
private ICollection<RunAlterationsResult> Results { get; set; } = new List<RunAlterationsResult>();
}
@@ -0,0 +1,12 @@
namespace Elsa.Api.Client.Resources.Alterations.Responses;
/// <summary>
/// The response to the "Submit" endpoint
/// </summary>
public class SubmitResponse
{
/// <summary>
/// The ID of the alteration plan created as part of the Submit request
/// </summary>
public string PlanId { get; set; } = string.Empty;
}
@@ -0,0 +1,25 @@
using Elsa.Api.Client.Resources.CommitStrategies.Models;
using Elsa.Api.Client.Shared.Models;
using Refit;
namespace Elsa.Api.Client.Resources.CommitStrategies.Contracts;
/// <summary>
/// Represents a client for the commit strategies API.
/// </summary>
public interface ICommitStrategiesApi
{
/// <summary>
/// Lists workflow commit strategies.
/// </summary>
/// <returns>A list response containing activity commit strategy descriptors and their count.</returns>
[Get("/descriptors/commit-strategies/workflows")]
Task<ListResponse<CommitStrategyDescriptor>> ListWorkflowStrategiesAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Lists activity commit strategies.
/// </summary>
/// <returns>A list response containing activity commit strategy descriptors and their count.</returns>
[Get("/descriptors/commit-strategies/activities")]
Task<ListResponse<CommitStrategyDescriptor>> ListActivityStrategiesAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,7 @@
namespace Elsa.Api.Client.Resources.CommitStrategies.Models;
/// <summary>
/// Represents a descriptor for a commit strategy, containing information such as its technical name,
/// display name, and description.
/// </summary>
public record CommitStrategyDescriptor(string Name, string DisplayName, string Description);
@@ -0,0 +1,23 @@
using Elsa.Api.Client.Resources.Features.Models;
using Elsa.Api.Client.Shared.Models;
using Refit;
namespace Elsa.Api.Client.Resources.Features.Contracts;
/// <summary>
/// A client for working with features.
/// </summary>
public interface IFeaturesApi
{
/// <summary>
/// Gets the specified feature.
/// </summary>
[Get("/features/installed/{fullName}")]
Task<FeatureDescriptor> GetAsync(string fullName, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the specified feature.
/// </summary>
[Get("/features/installed")]
Task<ListResponse<FeatureDescriptor>> ListAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,35 @@
using JetBrains.Annotations;
namespace Elsa.Api.Client.Resources.Features.Models;
/// <summary>
/// Represents a feature descriptor.
/// </summary>
[PublicAPI]
public class FeatureDescriptor
{
/// <summary>
/// Gets or sets the name of the feature.
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// Gets or sets the namespace of the feature.
/// </summary>
public string Namespace { get; set; } = default!;
/// <summary>
/// Gets the full name of the feature.
/// </summary>
public string FullName { get; set; } = default!;
/// <summary>
/// The display name for the feature.
/// </summary>
public string DisplayName { get; set; } = default!;
/// <summary>
/// The description of the feature.
/// </summary>
public string Description { get; set; } = "";
}
@@ -0,0 +1,20 @@
using Elsa.Api.Client.Resources.Identity.Requests;
using Elsa.Api.Client.Resources.Identity.Responses;
using Refit;
namespace Elsa.Api.Client.Resources.Identity.Contracts;
/// <summary>
/// Represents a client for the login API.
/// </summary>
public interface ILoginApi
{
/// <summary>
/// Sends the specified request to the login API.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The response.</returns>
[Post("/identity/login")]
Task<LoginResponse> LoginAsync([Body] LoginRequest request, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,8 @@
namespace Elsa.Api.Client.Resources.Identity.Requests;
/// <summary>
/// Represents a request to log in.
/// </summary>
/// <param name="Username">The username.</param>
/// <param name="Password">The password.</param>
public record LoginRequest(string Username, string Password);
@@ -0,0 +1,6 @@
namespace Elsa.Api.Client.Resources.Identity.Responses;
/// <summary>
/// The response of logging in.
/// </summary>
public record LoginResponse(bool IsAuthenticated, string? AccessToken, string? RefreshToken);
@@ -0,0 +1,17 @@
using Elsa.Api.Client.Resources.IncidentStrategies.Models;
using Elsa.Api.Client.Shared.Models;
using Refit;
namespace Elsa.Api.Client.Resources.IncidentStrategies.Contracts;
/// <summary>
/// Represents a client for the variable types API.
/// </summary>
public interface IIncidentStrategiesApi
{
/// <summary>
/// Lists incident strategies.
/// </summary>
[Get("/descriptors/incident-strategies")]
Task<ListResponse<IncidentStrategyDescriptor>> ListAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,9 @@
namespace Elsa.Api.Client.Resources.IncidentStrategies.Models;
/// <summary>
/// Represents an incident strategy.
/// </summary>
/// <param name="TypeName">The .NET type name of the strategy.</param>
/// <param name="DisplayName">The display name of the strategy.</param>
/// <param name="Description">The description of the strategy.</param>
public record IncidentStrategyDescriptor(string TypeName, string DisplayName, string? Description);
@@ -0,0 +1,19 @@
using Elsa.Api.Client.Shared.Models;
using JetBrains.Annotations;
using Refit;
namespace Elsa.Api.Client.Resources.LogPersistenceStrategies;
/// <summary>
/// Represents a client API for managing log persistence strategies.
/// </summary>
[UsedImplicitly]
public interface ILogPersistenceStrategiesApi
{
/// <summary>
/// Lists log persistence strategies.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
[Get("/descriptors/log-persistence-strategies")]
Task<ListResponse<LogPersistenceStrategyDescriptor>> ListAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,12 @@
using JetBrains.Annotations;
namespace Elsa.Api.Client.Resources.LogPersistenceStrategies;
/// <summary>
/// Represents a log persistence strategy.
/// </summary>
/// <param name="TypeName">The .NET type name of the log persistence strategy.</param>
/// <param name="DisplayName">The display name of the log persistence strategy.</param>
/// <param name="Description">The description of the log persistence strategy.</param>
[UsedImplicitly]
public record LogPersistenceStrategyDescriptor(string TypeName, string DisplayName, string? Description);
@@ -0,0 +1,16 @@
using Elsa.Api.Client.Resources.Package.Models;
using Refit;
namespace Elsa.Api.Client.Resources.Package.Contracts;
/// <summary>
/// A client for working with packages.
/// </summary>
public interface IPackageApi
{
/// <summary>
/// Gets the installed package version of Elsa.
/// </summary>
[Get("/package/version")]
Task<PackageVersionResponse> GetAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,7 @@
namespace Elsa.Api.Client.Resources.Package.Models;
/// <summary>
/// Contains the installed package version of Elsa.
/// </summary>
/// <param name="PackageVersion">The installed package version of Elsa.</param>
public record PackageVersionResponse(string PackageVersion);
@@ -0,0 +1,20 @@
using System.Text.Json.Nodes;
using Elsa.Api.Client.Shared.Models;
using Refit;
namespace Elsa.Api.Client.Resources.Resilience.Contracts;
/// <summary>
/// Provides operations for managing and retrieving resilience strategies within the application.
/// </summary>
public interface IResilienceStrategiesApi
{
/// <summary>
/// Retrieves a list of resilience strategies from the resilience strategies API endpoint.
/// </summary>
/// <param name="category">The category to filter the strategies by.</param>
/// <param name="cancellationToken">An optional token to cancel the operation.</param>
/// <returns>A <see cref="ListResponse{JsonObject}"/> with the list of resilience strategies.</returns>
[Get("/resilience/strategies")]
Task<ListResponse<JsonObject>> ListAsync(string category, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,11 @@
using Elsa.Api.Client.Resources.Resilience.Models;
using Elsa.Api.Client.Shared.Models;
using Refit;
namespace Elsa.Api.Client.Resources.Resilience.Contracts;
public interface IRetryAttemptsApi
{
[Get("/resilience/retries/{activityInstanceId}")]
Task<PagedListResponse<RetryAttemptRecord>> ListAsync(string activityInstanceId, int? skip = null, int? take = null, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,23 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using Elsa.Api.Client.Resources.Scripting.Models;
using Elsa.Api.Client.Serialization;
namespace Elsa.Api.Client.Resources.Resilience.Models;
public class ResilienceStrategyConfig
{
public ResilienceStrategyConfigMode Mode { get; set; }
public string? StrategyId { get; set; }
public Expression? Expression { get; set; }
public JsonNode SerializeToNode()
{
return JsonSerializer.SerializeToNode(this, SerializerOptions.ResilienceStrategyConfigSerializerOptions)!;
}
public static ResilienceStrategyConfig? Deserialize(JsonNode? node)
{
return node.Deserialize<ResilienceStrategyConfig?>(SerializerOptions.ResilienceStrategyConfigSerializerOptions)!;
}
}
@@ -0,0 +1,7 @@
namespace Elsa.Api.Client.Resources.Resilience.Models;
public enum ResilienceStrategyConfigMode
{
Identifier,
Expression,
}
@@ -0,0 +1,13 @@
using Elsa.Api.Client.Shared.Models;
namespace Elsa.Api.Client.Resources.Resilience.Models;
public class RetryAttemptRecord : Entity
{
public string ActivityInstanceId { get; set; } = null!;
public string ActivityId { get; set; } = null!;
public string WorkflowInstanceId { get; set; } = null!;
public int AttemptNumber { get; set; }
public TimeSpan RetryDelay { get; set; }
public IDictionary<string, string?> Details { get; set; }
}
@@ -0,0 +1,19 @@
using Elsa.Api.Client.Resources.Scripting.Models;
using Elsa.Api.Client.Shared.Models;
using JetBrains.Annotations;
using Refit;
namespace Elsa.Api.Client.Resources.Scripting.Contracts;
/// <summary>
/// Represents a client for the expression descriptors API.
/// </summary>
[PublicAPI]
public interface IExpressionDescriptorsApi
{
/// <summary>
/// Sends a request to retrieve all available expression descriptors.
/// </summary>
[Get("/descriptors/expression-descriptors")]
Task<ListResponse<ExpressionDescriptor>> ListAsync(CancellationToken cancellationToken);
}
@@ -0,0 +1,22 @@
using Elsa.Api.Client.Resources.Scripting.Requests;
using JetBrains.Annotations;
using Refit;
namespace Elsa.Api.Client.Resources.Scripting.Contracts;
/// <summary>
/// Represents a client for the workflow javascript API.
/// </summary>
[PublicAPI]
public interface IJavaScriptApi
{
/// <summary>
/// Dispatches a request to retrieve the type definitions of the specified workflow definition.
/// </summary>
/// <param name="definitionId">The definition ID of the workflow definition to retrieve the type definitions for.</param>
/// <param name="request">Required request containing properties to retrieve the correct type definitions.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>A response containing the type definition data which can be used to extend auto completion in Monaco.</returns>
[Post("/scripting/javascript/type-definitions/{definitionId}")]
Task<HttpResponseMessage> GetTypeDefinitions(string definitionId, GetWorkflowJavaScriptDefinitionRequest request, CancellationToken cancellationToken);
}
@@ -0,0 +1,16 @@
using Elsa.Api.Client.Resources.Scripting.Models;
using JetBrains.Annotations;
namespace Elsa.Api.Client.Resources.Scripting.Extensions;
/// <summary>
/// Provides extension methods for <see cref="Expression"/>.
/// </summary>
[PublicAPI]
public static class ExpressionDescriptorExtensions
{
/// <summary>
/// Gets the expression language to use for the Monaco editor.
/// </summary>
public static string? GetMonacoLanguage(this ExpressionDescriptor descriptor) => descriptor.Properties.TryGetValue("MonacoLanguage", out var language) ? language : null;
}
@@ -0,0 +1,53 @@
using System.Text.Json.Serialization;
namespace Elsa.Api.Client.Resources.Scripting.Models;
/// <summary>
/// Represents a dynamic expression.
/// </summary>
public class Expression
{
/// <summary>
/// Initializes a new instance of the <see cref="Expression"/> class.
/// </summary>
[JsonConstructor]
public Expression()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Expression"/> class.
/// </summary>
/// <param name="type">The type of the expression.</param>
/// <param name="value">The expression.</param>
public Expression(string type, string? value = null)
{
Type = type;
Value = value;
}
/// <summary>
/// Gets or sets the expression type.
/// </summary>
public string Type { get; set; } = null!;
/// <summary>
/// Gets or sets the value representing the expression.
/// </summary>
public object? Value { get; set; }
/// <summary>
/// Returns the C# expression.
/// </summary>
public override string ToString() => Value?.ToString() ?? "";
/// <summary>
/// Creates a literal expression.
/// </summary>
public static Expression CreateLiteral(string value) => new("Literal", value);
/// <summary>
/// Creates an object expression.
/// </summary>
public static Expression CreateObject(string value) => new("Object", value);
}
@@ -0,0 +1,53 @@
using System.Text.Json.Serialization;
namespace Elsa.Api.Client.Resources.Scripting.Models;
/// <summary>
/// Represents a descriptor for an expression.
/// </summary>
public class ExpressionDescriptor
{
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionDescriptor"/> class.
/// </summary>
[JsonConstructor]
public ExpressionDescriptor()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionDescriptor"/> class.
/// </summary>
/// <param name="type">The expression type.</param>
/// <param name="displayName">The display name of the expression type.</param>
public ExpressionDescriptor(string type, string displayName)
{
Type = type;
DisplayName = displayName;
}
/// <summary>
/// Gets or sets the expression type.
/// </summary>
public string Type { get; set; } = default!;
/// <summary>
/// Gets or sets the display name of the expression type.
/// </summary>
public string DisplayName { get; set; } = default!;
/// <summary>
/// Gets or sets whether the expression value is serializable.
/// </summary>
public bool IsSerializable { get; set; } = true;
/// <summary>
/// Gets or sets whether the expression type is browsable.
/// </summary>
public bool IsBrowsable { get; set; } = true;
/// <summary>
/// Gets or sets the expression type properties.
/// </summary>
public IDictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
}
@@ -0,0 +1,22 @@
namespace Elsa.Api.Client.Resources.Scripting.Requests;
/// <summary>
/// A request to retrieve the type definitions
/// </summary>
public class GetWorkflowJavaScriptDefinitionRequest(string workflowDefinitionId, string? activityTypeName, string? propertyName)
{
/// <summary>
/// Gets or sets workflow definition to retrieve type definitions for.
/// </summary>
public string WorkflowDefinitionId { get; set; } = workflowDefinitionId;
/// <summary>
/// Gets or sets the type name of the activity to retrieve type definitions for.
/// </summary>
public string? ActivityTypeName { get; set; } = activityTypeName;
/// <summary>
/// Gets or sets the name of the property to retrieve type definitions for.
/// </summary>
public string? PropertyName { get; set; } = propertyName;
}
@@ -0,0 +1,18 @@
using Elsa.Api.Client.Resources.StorageDrivers.Responses;
using Refit;
namespace Elsa.Api.Client.Resources.StorageDrivers.Contracts;
/// <summary>
/// Represents a client for the storage drivers API.
/// </summary>
public interface IStorageDriversApi
{
/// <summary>
/// Lists storage drivers.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The response containing the storage drivers.</returns>
[Get("/descriptors/storage-drivers")]
Task<ListStorageDriversResponse> ListAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,8 @@
namespace Elsa.Api.Client.Resources.StorageDrivers.Models;
/// <summary>
/// Represents a storage driver descriptor.
/// </summary>
/// <param name="TypeName">The type name of the storage driver.</param>
/// <param name="DisplayName">The display name of the storage driver.</param>
public record StorageDriverDescriptor(string TypeName, string DisplayName, double Priority = 0, bool Deprecated = false);
@@ -0,0 +1,9 @@
using Elsa.Api.Client.Resources.StorageDrivers.Models;
namespace Elsa.Api.Client.Resources.StorageDrivers.Responses;
/// <summary>
/// The response containing the storage drivers.
/// </summary>
/// <param name="Items">The storage drivers.</param>
public record ListStorageDriversResponse(ICollection<StorageDriverDescriptor> Items);
@@ -0,0 +1,20 @@
using Elsa.Api.Client.Resources.Tasks.Requests;
using Refit;
namespace Elsa.Api.Client.Resources.Tasks.Contracts;
/// <summary>
/// Represents a client for the tasks API.
/// </summary>
public interface ITasksApi
{
/// <summary>
/// Sends the specified request to the tasks API.
/// </summary>
/// <param name="taskId">The Id of the task being reported as completed.</param>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The response.</returns>
[Post("/tasks/{taskId}/complete")]
Task ReportTaskCompletedAsync(string taskId, [Body] ReportTaskCompletedRequest request, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,7 @@
namespace Elsa.Api.Client.Resources.Tasks.Requests;
/// <summary>
/// Represents a request to report a task has been completed.
/// </summary>
/// <param name="Result">The result of the task being reported as completed.</param>
public record ReportTaskCompletedRequest(object? Result);
@@ -0,0 +1,18 @@
using Elsa.Api.Client.Resources.VariableTypes.Responses;
using Refit;
namespace Elsa.Api.Client.Resources.VariableTypes.Contracts;
/// <summary>
/// Represents a client for the variable types API.
/// </summary>
public interface IVariableTypesApi
{
/// <summary>
/// Lists variable types.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The response containing the variable types.</returns>
[Get("/descriptors/variables")]
Task<ListVariableTypesResponse> ListAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,10 @@
namespace Elsa.Api.Client.Resources.VariableTypes.Models;
/// <summary>
/// Represents a variable type.
/// </summary>
/// <param name="TypeName">The .NET type name of the variable type.</param>
/// <param name="DisplayName">The display name of the variable type.</param>
/// <param name="Category">The category of the variable type.</param>
/// <param name="Description">The description of the variable type.</param>
public record VariableTypeDescriptor(string TypeName, string DisplayName, string Category, string? Description);
@@ -0,0 +1,9 @@
using Elsa.Api.Client.Resources.VariableTypes.Models;
namespace Elsa.Api.Client.Resources.VariableTypes.Responses;
/// <summary>
/// A response containing a list of variable types.
/// </summary>
/// <param name="Items">A list of variable types.</param>
public record ListVariableTypesResponse(ICollection<VariableTypeDescriptor> Items);
@@ -0,0 +1,18 @@
using Elsa.Api.Client.Resources.WorkflowActivationStrategies.Models;
using Elsa.Api.Client.Shared.Models;
using Refit;
namespace Elsa.Api.Client.Resources.WorkflowActivationStrategies.Contracts;
/// <summary>
/// Represents a client for the variable types API.
/// </summary>
public interface IWorkflowActivationStrategiesApi
{
/// <summary>
/// Lists workflow activation strategies.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
[Get("/descriptors/workflow-activation-strategies")]
Task<ListResponse<WorkflowActivationStrategyDescriptor>> ListAsync(CancellationToken cancellationToken = default);
}
@@ -0,0 +1,9 @@
namespace Elsa.Api.Client.Resources.WorkflowActivationStrategies.Models;
/// <summary>
/// Represents a workflow activation strategy.
/// </summary>
/// <param name="TypeName">The .NET type name of the activation strategy.</param>
/// <param name="DisplayName">The display name of the activation strategy.</param>
/// <param name="Description">The description of the activation strategy.</param>
public record WorkflowActivationStrategyDescriptor(string TypeName, string DisplayName, string? Description);

Some files were not shown because too many files have changed in this diff Show More