diff --git a/Directory.Packages.props b/Directory.Packages.props index 758fd10b..975cd9d1 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -11,6 +11,7 @@ + diff --git a/ScriptBee.sln b/ScriptBee.sln index 28dce80d..9c7e1564 100644 --- a/ScriptBee.sln +++ b/ScriptBee.sln @@ -53,6 +53,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.File", "src\Ada EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.File.Tests", "test\Adapters\Driven\Persistence.File.Tests\Persistence.File.Tests.csproj", "{86A8C10D-0701-4D44-BEAB-0982C0D733A2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ports.Plugins", "src\Application\Ports\Driven\Ports.Plugins\Ports.Plugins.csproj", "{FC474598-6399-46E2-8795-394E4F806052}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.InMemory", "src\Adapters\Driven\Persistence.InMemory\Persistence.InMemory.csproj", "{0426847C-4B33-434E-AC93-7C8C95AB5506}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Persistence.InMemory.Tests", "test\Adapters\Driven\Persistence.InMemory.Tests\Persistence.InMemory.Tests.csproj", "{348FE904-F636-4113-B63E-044B645F64C7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -163,5 +169,17 @@ Global {86A8C10D-0701-4D44-BEAB-0982C0D733A2}.Debug|Any CPU.Build.0 = Debug|Any CPU {86A8C10D-0701-4D44-BEAB-0982C0D733A2}.Release|Any CPU.ActiveCfg = Release|Any CPU {86A8C10D-0701-4D44-BEAB-0982C0D733A2}.Release|Any CPU.Build.0 = Release|Any CPU + {FC474598-6399-46E2-8795-394E4F806052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC474598-6399-46E2-8795-394E4F806052}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC474598-6399-46E2-8795-394E4F806052}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC474598-6399-46E2-8795-394E4F806052}.Release|Any CPU.Build.0 = Release|Any CPU + {0426847C-4B33-434E-AC93-7C8C95AB5506}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0426847C-4B33-434E-AC93-7C8C95AB5506}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0426847C-4B33-434E-AC93-7C8C95AB5506}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0426847C-4B33-434E-AC93-7C8C95AB5506}.Release|Any CPU.Build.0 = Release|Any CPU + {348FE904-F636-4113-B63E-044B645F64C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {348FE904-F636-4113-B63E-044B645F64C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {348FE904-F636-4113-B63E-044B645F64C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {348FE904-F636-4113-B63E-044B645F64C7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/ScriptBee/Models/Plugin.cs b/ScriptBee/Models/Plugin.cs deleted file mode 100644 index 2fcf9fff..00000000 --- a/ScriptBee/Models/Plugin.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using ScriptBee.Plugin.Manifest; - -namespace ScriptBee.Models; - -public record Plugin( - string FolderPath, - string Id, - Version Version, - PluginManifest Manifest -); diff --git a/ScriptBee/Plugin/IPluginRepository.cs b/ScriptBee/Plugin/IPluginRepository.cs deleted file mode 100644 index 85063b4e..00000000 --- a/ScriptBee/Plugin/IPluginRepository.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using DxWorks.ScriptBee.Plugin.Api; -using ScriptBee.Plugin.Manifest; - -namespace ScriptBee.Plugin; - -public interface IPluginRepository -{ - void UnRegisterPlugin(string pluginId, string pluginVersion); - - void RegisterPlugin(Models.Plugin plugin, Type @interface, Type concrete); - - void RegisterPlugin(Models.Plugin plugin); - - TService? GetPlugin(Func filter, - IEnumerable<(Type @interface, object instance)>? services = null) - where TService : IPlugin; - - IEnumerable GetPlugins(IEnumerable<(Type @interface, object instance)>? services = null) - where TService : IPlugin; - - IEnumerable GetLoadedPluginsManifests(); - - IEnumerable GetLoadedPlugins(string kind); - - IEnumerable GetLoadedPluginExtensionPoints() - where T : PluginExtensionPoint; - - Version? GetInstalledPluginVersion(string pluginId); -} diff --git a/ScriptBee/Plugin/Manifest/LinkerPluginExtensionPoint.cs b/ScriptBee/Plugin/Manifest/LinkerPluginExtensionPoint.cs deleted file mode 100644 index 01e58b95..00000000 --- a/ScriptBee/Plugin/Manifest/LinkerPluginExtensionPoint.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ScriptBee.Plugin.Manifest; - -public class LinkerPluginExtensionPoint : PluginExtensionPoint -{ -} diff --git a/ScriptBee/Plugin/Manifest/LoaderPluginExtensionPoint.cs b/ScriptBee/Plugin/Manifest/LoaderPluginExtensionPoint.cs deleted file mode 100644 index c8544343..00000000 --- a/ScriptBee/Plugin/Manifest/LoaderPluginExtensionPoint.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ScriptBee.Plugin.Manifest; - -public class LoaderPluginExtensionPoint : PluginExtensionPoint -{ -} diff --git a/ScriptBee/Plugin/Manifest/PluginBundleExtensionPoint.cs b/ScriptBee/Plugin/Manifest/PluginBundleExtensionPoint.cs deleted file mode 100644 index 628f75b0..00000000 --- a/ScriptBee/Plugin/Manifest/PluginBundleExtensionPoint.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace ScriptBee.Plugin.Manifest; - -public class PluginBundleExtensionPoint : PluginExtensionPoint -{ -} diff --git a/ScriptBee/Plugin/PluginRepository.cs b/ScriptBee/Plugin/PluginRepository.cs deleted file mode 100644 index 8ab91932..00000000 --- a/ScriptBee/Plugin/PluginRepository.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using DxWorks.ScriptBee.Plugin.Api; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using ScriptBee.Plugin.Manifest; - -namespace ScriptBee.Plugin; - -// todo add tests -public class PluginRepository : IPluginRepository -{ - private readonly ConcurrentDictionary _plugins = new(); - private readonly ConcurrentBag _pluginServiceCollection = new(); - - public void UnRegisterPlugin(string pluginId, string pluginVersion) - { - _plugins.Remove(pluginId, out _); - } - - public void RegisterPlugin(Models.Plugin plugin, Type @interface, Type concrete) - { - RegisterPlugin(plugin); - - _pluginServiceCollection.Add(new ServiceDescriptor(@interface, concrete, ServiceLifetime.Singleton)); - } - - public void RegisterPlugin(Models.Plugin plugin) - { - _plugins.AddOrUpdate(plugin.Id, plugin, (_, manifest) => manifest.Version < plugin.Version ? plugin : manifest); - } - - public TService? GetPlugin(Func filter, - IEnumerable<(Type @interface, object instance)>? services = null) where TService : IPlugin - { - return GetPlugins().FirstOrDefault(filter); - } - - public IEnumerable GetPlugins(IEnumerable<(Type @interface, object instance)>? services = null) - where TService : IPlugin - { - var serviceCollection = new ServiceCollection - { - _pluginServiceCollection - }; - - if (services is not null) - serviceCollection.Add(services.Select(s => new ServiceDescriptor(s.@interface, s.instance))); - - return serviceCollection.BuildServiceProvider() - .GetServices(); - } - - public IEnumerable GetLoadedPluginsManifests() - { - return GetLoadedPlugins().Select(plugin => plugin.Manifest); - } - - public IEnumerable GetLoadedPlugins(string kind) - { - return GetLoadedPlugins() - .Where(plugin => plugin.Manifest.ExtensionPoints.Any(extensionPoint => extensionPoint.Kind == kind)); - } - - public IEnumerable GetLoadedPluginExtensionPoints() where T : PluginExtensionPoint - { - return GetLoadedPlugins().SelectMany(p => p.Manifest.ExtensionPoints).OfType(); - } - - public Version? GetInstalledPluginVersion(string pluginId) - { - return _plugins.TryGetValue(pluginId, out var plugin) ? plugin.Version : null; - } - - private IEnumerable GetLoadedPlugins() - { - return _plugins.Values; - } -} diff --git a/ScriptBeeWebApp/Services/GuidGenerator.cs b/ScriptBeeWebApp/Services/GuidGenerator.cs deleted file mode 100644 index fef28364..00000000 --- a/ScriptBeeWebApp/Services/GuidGenerator.cs +++ /dev/null @@ -1,11 +0,0 @@ -using ScriptBee.Services; - -namespace ScriptBeeWebApp.Services; - -internal sealed class GuidGenerator : IGuidGenerator -{ - public Guid GenerateGuid() - { - return Guid.NewGuid(); - } -} diff --git a/calculation.Dockerfile b/calculation.Dockerfile index b7584fa9..a2e05771 100644 --- a/calculation.Dockerfile +++ b/calculation.Dockerfile @@ -15,10 +15,12 @@ COPY src/Application/Domain/Service.Analysis src/Application/Domain/Service.Anal COPY src/Application/Ports/Driving/UseCases.Analysis src/Application/Ports/Driving/UseCases.Analysis COPY src/Application/Ports/Driven/Ports.Analysis src/Application/Ports/Driven/Ports.Analysis COPY src/Application/Ports/Driven/Ports.Files src/Application/Ports/Driven/Ports.Files +COPY src/Application/Ports/Driven/Ports.Plugins src/Application/Ports/Driven/Ports.Plugins COPY src/Application/Ports/Driven/Ports.Project src/Application/Ports/Driven/Ports.Project -COPY src/Adapters/Driven/Persistence.Mongodb src/Adapters/Driven/Persistence.Mongodb COPY src/Adapters/Driven/Persistence.File src/Adapters/Driven/Persistence.File +COPY src/Adapters/Driven/Persistence.InMemory src/Adapters/Driven/Persistence.InMemory +COPY src/Adapters/Driven/Persistence.Mongodb src/Adapters/Driven/Persistence.Mongodb COPY src/Adapters/Driving/Common.Web src/Adapters/Driving/Common.Web COPY src/Adapters/Driving/Calculation.Web src/Adapters/Driving/Calculation.Web diff --git a/src/Adapters/Driven/Persistence.InMemory/Persistence.InMemory.csproj b/src/Adapters/Driven/Persistence.InMemory/Persistence.InMemory.csproj new file mode 100644 index 00000000..33f95daf --- /dev/null +++ b/src/Adapters/Driven/Persistence.InMemory/Persistence.InMemory.csproj @@ -0,0 +1,13 @@ + + + ScriptBee.Persistence.InMemory + + + + + + + + + + diff --git a/src/Adapters/Driven/Persistence.InMemory/PluginRepository.cs b/src/Adapters/Driven/Persistence.InMemory/PluginRepository.cs new file mode 100644 index 00000000..11a950bb --- /dev/null +++ b/src/Adapters/Driven/Persistence.InMemory/PluginRepository.cs @@ -0,0 +1,91 @@ +using System.Collections.Concurrent; +using DxWorks.ScriptBee.Plugin.Api; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using ScriptBee.Domain.Model.Plugin; +using ScriptBee.Domain.Model.Plugin.Manifest; +using ScriptBee.Ports.Plugins; + +namespace ScriptBee.Persistence.InMemory; + +public class PluginRepository : IPluginRepository +{ + private readonly ConcurrentDictionary _plugins = new(); + private readonly ConcurrentBag _pluginServiceCollection = []; + + public void UnRegisterPlugin(string pluginId, string pluginVersion) + { + _plugins.Remove(pluginId, out _); + } + + public void RegisterPlugin(Plugin plugin, Type @interface, Type concrete) + { + RegisterPlugin(plugin); + + _pluginServiceCollection.Add( + new ServiceDescriptor(@interface, concrete, ServiceLifetime.Singleton) + ); + } + + public void RegisterPlugin(Plugin plugin) + { + _plugins.AddOrUpdate( + plugin.Id, + plugin, + (_, manifest) => manifest.Version < plugin.Version ? plugin : manifest + ); + } + + public TService? GetPlugin( + Func filter, + IEnumerable<(Type @interface, object instance)>? services = null + ) + where TService : IPlugin + { + return GetPlugins().FirstOrDefault(filter); + } + + public IEnumerable GetPlugins( + IEnumerable<(Type @interface, object instance)>? services = null + ) + where TService : IPlugin + { + var serviceCollection = new ServiceCollection { _pluginServiceCollection }; + + if (services is not null) + serviceCollection.Add( + services.Select(s => new ServiceDescriptor(s.@interface, s.instance)) + ); + + return serviceCollection.BuildServiceProvider().GetServices(); + } + + public IEnumerable GetLoadedPluginsManifests() + { + return GetLoadedPlugins().Select(plugin => plugin.Manifest); + } + + public IEnumerable GetLoadedPlugins(string kind) + { + return GetLoadedPlugins() + .Where(plugin => + plugin.Manifest.ExtensionPoints.Any(extensionPoint => extensionPoint.Kind == kind) + ); + } + + public IEnumerable GetLoadedPluginExtensionPoints() + where T : PluginExtensionPoint + { + return GetLoadedPlugins().SelectMany(p => p.Manifest.ExtensionPoints).OfType(); + } + + public Version? GetInstalledPluginVersion(string pluginId) + { + return _plugins.TryGetValue(pluginId, out var plugin) ? plugin.Version : null; + } + + private IEnumerable GetLoadedPlugins() + { + return _plugins.Values; + } +} diff --git a/src/Adapters/Driving/Analysis.Web/Analysis.Web.csproj b/src/Adapters/Driving/Analysis.Web/Analysis.Web.csproj index cd75827e..b9c7e879 100644 --- a/src/Adapters/Driving/Analysis.Web/Analysis.Web.csproj +++ b/src/Adapters/Driving/Analysis.Web/Analysis.Web.csproj @@ -10,6 +10,7 @@ + diff --git a/src/Adapters/Driving/Analysis.Web/Extensions/PluginsConfigExtensions.cs b/src/Adapters/Driving/Analysis.Web/Extensions/PluginsConfigExtensions.cs new file mode 100644 index 00000000..a506a196 --- /dev/null +++ b/src/Adapters/Driving/Analysis.Web/Extensions/PluginsConfigExtensions.cs @@ -0,0 +1,12 @@ +using ScriptBee.Persistence.InMemory; +using ScriptBee.Ports.Plugins; + +namespace ScriptBee.Analysis.Web.Extensions; + +public static class PluginsConfigExtensions +{ + public static IServiceCollection AddPluginsConfig(this IServiceCollection services) + { + return services.AddSingleton(); + } +} diff --git a/src/Adapters/Driving/Analysis.Web/Program.cs b/src/Adapters/Driving/Analysis.Web/Program.cs index f71efd69..87699d6f 100644 --- a/src/Adapters/Driving/Analysis.Web/Program.cs +++ b/src/Adapters/Driving/Analysis.Web/Program.cs @@ -20,7 +20,8 @@ .AddProblemDetailsDefaults() .AddMongoDb(mongoConnectionString) .AddCommonServices() - .AddFileConfig(userFolderConfigurationSection); + .AddFileConfig(userFolderConfigurationSection) + .AddPluginsConfig(); builder.Services.AddEndpointDefinitions( typeof(IEndpointDefinition), diff --git a/ScriptBee/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs similarity index 50% rename from ScriptBee/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs rename to src/Application/Domain/Model/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs index f0fb1d73..437e6438 100644 --- a/ScriptBee/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs +++ b/src/Application/Domain/Model/Plugin/Manifest/HelperFunctionsPluginExtensionPoint.cs @@ -1,5 +1,3 @@ -namespace ScriptBee.Plugin.Manifest; +namespace ScriptBee.Domain.Model.Plugin.Manifest; -public class HelperFunctionsPluginExtensionPoint : PluginExtensionPoint -{ -} +public class HelperFunctionsPluginExtensionPoint : PluginExtensionPoint { } diff --git a/src/Application/Domain/Model/Plugin/Manifest/LinkerPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/LinkerPluginExtensionPoint.cs new file mode 100644 index 00000000..0756bba5 --- /dev/null +++ b/src/Application/Domain/Model/Plugin/Manifest/LinkerPluginExtensionPoint.cs @@ -0,0 +1,3 @@ +namespace ScriptBee.Domain.Model.Plugin.Manifest; + +public class LinkerPluginExtensionPoint : PluginExtensionPoint { } diff --git a/src/Application/Domain/Model/Plugin/Manifest/LoaderPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/LoaderPluginExtensionPoint.cs new file mode 100644 index 00000000..246a74ae --- /dev/null +++ b/src/Application/Domain/Model/Plugin/Manifest/LoaderPluginExtensionPoint.cs @@ -0,0 +1,3 @@ +namespace ScriptBee.Domain.Model.Plugin.Manifest; + +public class LoaderPluginExtensionPoint : PluginExtensionPoint { } diff --git a/src/Application/Domain/Model/Plugin/Manifest/PluginBundleExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/PluginBundleExtensionPoint.cs new file mode 100644 index 00000000..517accf9 --- /dev/null +++ b/src/Application/Domain/Model/Plugin/Manifest/PluginBundleExtensionPoint.cs @@ -0,0 +1,3 @@ +namespace ScriptBee.Domain.Model.Plugin.Manifest; + +public class PluginBundleExtensionPoint : PluginExtensionPoint { } diff --git a/ScriptBee/Plugin/Manifest/PluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/PluginExtensionPoint.cs similarity index 77% rename from ScriptBee/Plugin/Manifest/PluginExtensionPoint.cs rename to src/Application/Domain/Model/Plugin/Manifest/PluginExtensionPoint.cs index 4b1123a2..83b91e3b 100644 --- a/ScriptBee/Plugin/Manifest/PluginExtensionPoint.cs +++ b/src/Application/Domain/Model/Plugin/Manifest/PluginExtensionPoint.cs @@ -1,4 +1,4 @@ -namespace ScriptBee.Plugin.Manifest; +namespace ScriptBee.Domain.Model.Plugin.Manifest; public abstract class PluginExtensionPoint { diff --git a/ScriptBee/Plugin/Manifest/PluginKind.cs b/src/Application/Domain/Model/Plugin/Manifest/PluginKind.cs similarity index 87% rename from ScriptBee/Plugin/Manifest/PluginKind.cs rename to src/Application/Domain/Model/Plugin/Manifest/PluginKind.cs index 5005ddec..cea7c2c6 100644 --- a/ScriptBee/Plugin/Manifest/PluginKind.cs +++ b/src/Application/Domain/Model/Plugin/Manifest/PluginKind.cs @@ -1,4 +1,4 @@ -namespace ScriptBee.Plugin.Manifest; +namespace ScriptBee.Domain.Model.Plugin.Manifest; public static class PluginKind { diff --git a/ScriptBee/Plugin/Manifest/PluginManifest.cs b/src/Application/Domain/Model/Plugin/Manifest/PluginManifest.cs similarity index 80% rename from ScriptBee/Plugin/Manifest/PluginManifest.cs rename to src/Application/Domain/Model/Plugin/Manifest/PluginManifest.cs index 5a09ebfa..6a3efea3 100644 --- a/ScriptBee/Plugin/Manifest/PluginManifest.cs +++ b/src/Application/Domain/Model/Plugin/Manifest/PluginManifest.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; - -namespace ScriptBee.Plugin.Manifest; +namespace ScriptBee.Domain.Model.Plugin.Manifest; public class PluginManifest { diff --git a/ScriptBee/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs similarity index 76% rename from ScriptBee/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs rename to src/Application/Domain/Model/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs index 1db6a7dc..ac56fda0 100644 --- a/ScriptBee/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs +++ b/src/Application/Domain/Model/Plugin/Manifest/ScriptGeneratorPluginExtensionPoint.cs @@ -1,4 +1,4 @@ -namespace ScriptBee.Plugin.Manifest; +namespace ScriptBee.Domain.Model.Plugin.Manifest; public class ScriptGeneratorPluginExtensionPoint : PluginExtensionPoint { diff --git a/ScriptBee/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs similarity index 69% rename from ScriptBee/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs rename to src/Application/Domain/Model/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs index 282db994..5a0dc0df 100644 --- a/ScriptBee/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs +++ b/src/Application/Domain/Model/Plugin/Manifest/ScriptRunnerPluginExtensionPoint.cs @@ -1,4 +1,4 @@ -namespace ScriptBee.Plugin.Manifest; +namespace ScriptBee.Domain.Model.Plugin.Manifest; public class ScriptRunnerPluginExtensionPoint : PluginExtensionPoint { diff --git a/ScriptBee/Plugin/Manifest/UiPluginExtensionPoint.cs b/src/Application/Domain/Model/Plugin/Manifest/UiPluginExtensionPoint.cs similarity index 85% rename from ScriptBee/Plugin/Manifest/UiPluginExtensionPoint.cs rename to src/Application/Domain/Model/Plugin/Manifest/UiPluginExtensionPoint.cs index aaa0fdcd..ddf6f9be 100644 --- a/ScriptBee/Plugin/Manifest/UiPluginExtensionPoint.cs +++ b/src/Application/Domain/Model/Plugin/Manifest/UiPluginExtensionPoint.cs @@ -1,4 +1,4 @@ -namespace ScriptBee.Plugin.Manifest; +namespace ScriptBee.Domain.Model.Plugin.Manifest; public class UiPluginExtensionPoint : PluginExtensionPoint { diff --git a/src/Application/Domain/Model/Plugin/Plugin.cs b/src/Application/Domain/Model/Plugin/Plugin.cs new file mode 100644 index 00000000..13fb48cc --- /dev/null +++ b/src/Application/Domain/Model/Plugin/Plugin.cs @@ -0,0 +1,5 @@ +using ScriptBee.Domain.Model.Plugin.Manifest; + +namespace ScriptBee.Domain.Model.Plugin; + +public record Plugin(string FolderPath, string Id, Version Version, PluginManifest Manifest); diff --git a/src/Application/Domain/Model/Plugin/ScriptRunnerNotFoundError.cs b/src/Application/Domain/Model/Plugin/ScriptRunnerNotFoundError.cs new file mode 100644 index 00000000..cb4e2782 --- /dev/null +++ b/src/Application/Domain/Model/Plugin/ScriptRunnerNotFoundError.cs @@ -0,0 +1,11 @@ +using ScriptBee.Domain.Model.ProjectStructure; + +namespace ScriptBee.Domain.Model.Plugin; + +public record ScriptRunnerNotFoundError(ScriptLanguage Language) +{ + public override string ToString() + { + return $"Runner for language '{Language.Name}' does not exist."; + } +} diff --git a/src/Application/Domain/Service.Analysis/RunAnalysisService.cs b/src/Application/Domain/Service.Analysis/RunAnalysisService.cs index 2d441b94..60a5cfe4 100644 --- a/src/Application/Domain/Service.Analysis/RunAnalysisService.cs +++ b/src/Application/Domain/Service.Analysis/RunAnalysisService.cs @@ -1,8 +1,12 @@ -using ScriptBee.Common; +using DxWorks.ScriptBee.Plugin.Api; +using OneOf; +using ScriptBee.Common; using ScriptBee.Domain.Model.Analysis; +using ScriptBee.Domain.Model.Plugin; using ScriptBee.Domain.Model.ProjectStructure; using ScriptBee.Ports.Analysis; using ScriptBee.Ports.Files; +using ScriptBee.Ports.Plugins; using ScriptBee.Ports.Project.Structure; using ScriptBee.UseCases.Analysis; @@ -13,7 +17,8 @@ public class RunAnalysisService( IGuidProvider guidProvider, ICreateAnalysis createAnalysis, IGetScript getScript, - ILoadFile loadFile + ILoadFile loadFile, + IPluginRepository pluginRepository ) : IRunAnalysisUseCase { public async Task Run( @@ -43,6 +48,30 @@ private async Task Run( Script script, CancellationToken cancellationToken = default ) + { + var scriptRunnerResult = GetScriptRunner(script.ScriptLanguage); + + return await scriptRunnerResult.Match( + runner => Run(runner, script, cancellationToken), + async error => + await createAnalysis.Create( + AnalysisInfo.FailedToStart( + new AnalysisId(guidProvider.NewGuid()), + script.ProjectId, + script.Id, + dateTimeProvider.UtcNow(), + error.ToString() + ), + cancellationToken + ) + ); + } + + private async Task Run( + IScriptRunner scriptRunner, + Script script, + CancellationToken cancellationToken = default + ) { var analysisInfo = await createAnalysis.Create( AnalysisInfo.Started( @@ -59,4 +88,17 @@ private async Task Run( return analysisInfo; } + + private OneOf GetScriptRunner( + ScriptLanguage scriptLanguage + ) + { + var scriptRunner = pluginRepository.GetPlugin(runner => + runner.Language == scriptLanguage.Name + ); + + return scriptRunner is null + ? new ScriptRunnerNotFoundError(scriptLanguage) + : OneOf.FromT0(scriptRunner); + } } diff --git a/src/Application/Domain/Service.Analysis/Service.Analysis.csproj b/src/Application/Domain/Service.Analysis/Service.Analysis.csproj index abe2669e..52f8af47 100644 --- a/src/Application/Domain/Service.Analysis/Service.Analysis.csproj +++ b/src/Application/Domain/Service.Analysis/Service.Analysis.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Application/Ports/Driven/Ports.Plugins/IPluginRepository.cs b/src/Application/Ports/Driven/Ports.Plugins/IPluginRepository.cs new file mode 100644 index 00000000..6e143d13 --- /dev/null +++ b/src/Application/Ports/Driven/Ports.Plugins/IPluginRepository.cs @@ -0,0 +1,34 @@ +using DxWorks.ScriptBee.Plugin.Api; +using ScriptBee.Domain.Model.Plugin; +using ScriptBee.Domain.Model.Plugin.Manifest; + +namespace ScriptBee.Ports.Plugins; + +public interface IPluginRepository +{ + void UnRegisterPlugin(string pluginId, string pluginVersion); + + void RegisterPlugin(Plugin plugin, Type @interface, Type concrete); + + void RegisterPlugin(Plugin plugin); + + TService? GetPlugin( + Func filter, + IEnumerable<(Type @interface, object instance)>? services = null + ) + where TService : IPlugin; + + IEnumerable GetPlugins( + IEnumerable<(Type @interface, object instance)>? services = null + ) + where TService : IPlugin; + + IEnumerable GetLoadedPluginsManifests(); + + IEnumerable GetLoadedPlugins(string kind); + + IEnumerable GetLoadedPluginExtensionPoints() + where T : PluginExtensionPoint; + + Version? GetInstalledPluginVersion(string pluginId); +} diff --git a/src/Application/Ports/Driven/Ports.Plugins/Ports.Plugins.csproj b/src/Application/Ports/Driven/Ports.Plugins/Ports.Plugins.csproj new file mode 100644 index 00000000..7565f998 --- /dev/null +++ b/src/Application/Ports/Driven/Ports.Plugins/Ports.Plugins.csproj @@ -0,0 +1,9 @@ + + + ScriptBee.Ports.Plugins + + + + + + diff --git a/test/Adapters/Driven/Persistence.InMemory.Tests/Persistence.InMemory.Tests.csproj b/test/Adapters/Driven/Persistence.InMemory.Tests/Persistence.InMemory.Tests.csproj new file mode 100644 index 00000000..86b16b40 --- /dev/null +++ b/test/Adapters/Driven/Persistence.InMemory.Tests/Persistence.InMemory.Tests.csproj @@ -0,0 +1,9 @@ + + + ScriptBee.Persistence.InMemory.Tests + + + + + + diff --git a/test/Adapters/Driven/Persistence.InMemory.Tests/PluginRepositoryTest.cs b/test/Adapters/Driven/Persistence.InMemory.Tests/PluginRepositoryTest.cs new file mode 100644 index 00000000..57614ea1 --- /dev/null +++ b/test/Adapters/Driven/Persistence.InMemory.Tests/PluginRepositoryTest.cs @@ -0,0 +1,214 @@ +using DxWorks.ScriptBee.Plugin.Api; +using ScriptBee.Domain.Model.Plugin; +using ScriptBee.Domain.Model.Plugin.Manifest; + +namespace ScriptBee.Persistence.InMemory.Tests; + +public class PluginRepositoryTests +{ + private readonly PluginRepository _repository = new(); + + [Fact] + public void RegisterPlugin_AddsPluginToRepository() + { + var plugin = CreateTestPlugin("testId", new Version(1, 0, 0)); + + _repository.RegisterPlugin(plugin); + + Assert.Single(_repository.GetLoadedPluginsManifests()); + Assert.Equal("testId", _repository.GetLoadedPluginsManifests().First().Name); + } + + [Fact] + public void RegisterPlugin_ReplacesOlderVersion() + { + var plugin1 = CreateTestPlugin("testId", new Version(1, 0, 0)); + var plugin2 = CreateTestPlugin("testId", new Version(2, 0, 0)); + + _repository.RegisterPlugin(plugin1); + _repository.RegisterPlugin(plugin2); + + Assert.Single(_repository.GetLoadedPluginsManifests()); + Assert.Equal(new Version(2, 0, 0), _repository.GetInstalledPluginVersion("testId")); + } + + [Fact] + public void RegisterPlugin_DoesNotReplaceNewerVersion() + { + var plugin1 = CreateTestPlugin("testId", new Version(2, 0, 0)); + var plugin2 = CreateTestPlugin("testId", new Version(1, 0, 0)); + + _repository.RegisterPlugin(plugin1); + _repository.RegisterPlugin(plugin2); + + Assert.Single(_repository.GetLoadedPluginsManifests()); + Assert.Equal(new Version(2, 0, 0), _repository.GetInstalledPluginVersion("testId")); + } + + [Fact] + public void UnRegisterPlugin_RemovesPluginFromRepository() + { + var plugin = CreateTestPlugin("testId", new Version(1, 0, 0)); + _repository.RegisterPlugin(plugin); + + _repository.UnRegisterPlugin("testId", "1.0.0"); + + Assert.Empty(_repository.GetLoadedPluginsManifests()); + } + + [Fact] + public void RegisterPlugin_WithInterfaceAndConcrete_AddsServiceDescriptor() + { + var plugin = CreateTestPlugin("testId", new Version(1, 0, 0)); + + _repository.RegisterPlugin(plugin, typeof(ITestPlugin), typeof(TestPlugin)); + + var services = _repository.GetPlugins(); + Assert.Single(services); + } + + [Fact] + public void GetPlugin_ReturnsPluginMatchingFilter() + { + var plugin1 = CreateTestPlugin("testId1", new Version(1, 0, 0)); + var plugin2 = CreateTestPlugin("testId2", new Version(1, 0, 0)); + _repository.RegisterPlugin(plugin1, typeof(ITestPlugin), typeof(TestPlugin)); + _repository.RegisterPlugin(plugin2, typeof(ITestPlugin), typeof(TestPlugin2)); + + var result = _repository.GetPlugin(p => p.GetPluginId() == "testId2"); + + Assert.NotNull(result); + Assert.Equal("testId2", result.GetPluginId()); + } + + [Fact] + public void GetPlugins_ReturnsAllPluginsOfGivenType() + { + var plugin1 = CreateTestPlugin("testId1", new Version(1, 0, 0)); + var plugin2 = CreateTestPlugin("testId2", new Version(1, 0, 0)); + _repository.RegisterPlugin(plugin1, typeof(ITestPlugin), typeof(TestPlugin)); + _repository.RegisterPlugin(plugin2, typeof(ITestPlugin), typeof(TestPlugin2)); + + var result = _repository.GetPlugins(); + + Assert.Equal(2, result.Count()); + } + + [Fact] + public void GetLoadedPluginsManifests_ReturnsManifestsOfLoadedPlugins() + { + var plugin1 = CreateTestPlugin("testId1", new Version(1, 0, 0)); + var plugin2 = CreateTestPlugin("testId2", new Version(1, 0, 0)); + _repository.RegisterPlugin(plugin1); + _repository.RegisterPlugin(plugin2); + + var manifests = _repository.GetLoadedPluginsManifests().ToList(); + + Assert.Equal(2, manifests.Count); + Assert.Contains(manifests, m => m.Name == "testId1"); + Assert.Contains(manifests, m => m.Name == "testId2"); + } + + [Fact] + public void GetLoadedPlugins_WithKind_ReturnsPluginsWithMatchingKind() + { + var plugin1 = CreateTestPlugin( + "testId1", + new Version(1, 0, 0), + new TestExtensionPoint { Kind = "testKind" } + ); + var plugin2 = CreateTestPlugin( + "testId2", + new Version(1, 0, 0), + new TestExtensionPoint { Kind = "anotherKind" } + ); + _repository.RegisterPlugin(plugin1); + _repository.RegisterPlugin(plugin2); + + var plugins = _repository.GetLoadedPlugins("testKind").ToList(); + + Assert.Single(plugins); + Assert.Equal("testId1", plugins.First().Id); + } + + [Fact] + public void GetLoadedPluginExtensionPoints_ReturnsExtensionPointsOfGivenType() + { + var plugin1 = CreateTestPlugin( + "testId1", + new Version(1, 0, 0), + new TestExtensionPoint { Kind = "testKind" } + ); + var plugin2 = CreateTestPlugin( + "testId2", + new Version(1, 0, 0), + new AnotherTestExtensionPoint { Kind = "anotherKind" } + ); + _repository.RegisterPlugin(plugin1); + _repository.RegisterPlugin(plugin2); + + var extensionPoints = _repository + .GetLoadedPluginExtensionPoints() + .ToList(); + + Assert.Single(extensionPoints); + Assert.Equal("testKind", extensionPoints.First().Kind); + } + + [Fact] + public void GetInstalledPluginVersion_ReturnsInstalledPluginVersion() + { + var plugin = CreateTestPlugin("testId", new Version(1, 0, 0)); + _repository.RegisterPlugin(plugin); + + var version = _repository.GetInstalledPluginVersion("testId"); + Assert.Equal(new Version(1, 0, 0), version); + } + + [Fact] + public void GetInstalledPluginVersion_ReturnsNullIfPluginNotInstalled() + { + var version = _repository.GetInstalledPluginVersion("nonExistentId"); + + Assert.Null(version); + } + + private static Plugin CreateTestPlugin( + string id, + Version version, + params PluginExtensionPoint[] extensionPoints + ) + { + return new Plugin( + "test/path", + id, + version, + new PluginManifest { Name = id, ExtensionPoints = extensionPoints.ToList() } + ); + } +} + +file class TestExtensionPoint : PluginExtensionPoint; + +file class AnotherTestExtensionPoint : PluginExtensionPoint; + +file interface ITestPlugin : IPlugin +{ + string GetPluginId(); +} + +file class TestPlugin : ITestPlugin +{ + public string GetPluginId() + { + return "testId1"; + } +} + +file class TestPlugin2 : ITestPlugin +{ + public string GetPluginId() + { + return "testId2"; + } +} diff --git a/test/Application/Domain/Service.Analysis.Tests/RunAnalysisServiceTest.cs b/test/Application/Domain/Service.Analysis.Tests/RunAnalysisServiceTest.cs index a1c0cd3a..dd3a5b35 100644 --- a/test/Application/Domain/Service.Analysis.Tests/RunAnalysisServiceTest.cs +++ b/test/Application/Domain/Service.Analysis.Tests/RunAnalysisServiceTest.cs @@ -1,4 +1,5 @@ -using NSubstitute; +using DxWorks.ScriptBee.Plugin.Api; +using NSubstitute; using OneOf; using ScriptBee.Common; using ScriptBee.Domain.Model.Analysis; @@ -6,6 +7,7 @@ using ScriptBee.Domain.Model.ProjectStructure; using ScriptBee.Ports.Analysis; using ScriptBee.Ports.Files; +using ScriptBee.Ports.Plugins; using ScriptBee.Ports.Project.Structure; using ScriptBee.Service.Analysis; using ScriptBee.UseCases.Analysis; @@ -19,6 +21,9 @@ public class RunAnalysisServiceTest private readonly ICreateAnalysis _createAnalysis = Substitute.For(); private readonly IGetScript _getScript = Substitute.For(); private readonly ILoadFile _loadFile = Substitute.For(); + private readonly IPluginRepository _pluginRepository = Substitute.For(); + + private readonly IScriptRunner _scriptRunner = Substitute.For(); private readonly RunAnalysisService _runAnalysisService; @@ -29,7 +34,8 @@ public RunAnalysisServiceTest() _guidProvider, _createAnalysis, _getScript, - _loadFile + _loadFile, + _pluginRepository ); } @@ -74,6 +80,7 @@ public async Task CreateAnalysisSuccessful() Arg.Any() ) .Returns(expectedAnalysisInfo); + _pluginRepository.GetPlugin(Arg.Any>()).Returns(_scriptRunner); var analysisResult = await _runAnalysisService.Run(command); @@ -83,6 +90,56 @@ await _loadFile .GetScriptContent(projectId, "path", Arg.Any()); } + [Fact] + public async Task GivenScriptRunnerDoesNotExistsError_thenAnalysisIsFailed() + { + var date = DateTimeOffset.UtcNow; + var projectId = ProjectId.FromValue("project-id"); + var analysisId = new AnalysisId(Guid.Parse("97c3498a-1a8f-4e01-be79-0cc9ede9f7a8")); + var scriptId = new ScriptId(Guid.Parse("38900b32-fca2-4213-bd4b-c71cc1068bb6")); + var command = new RunAnalysisCommand(projectId, scriptId); + var expectedAnalysisInfo = new AnalysisInfo( + analysisId, + projectId, + scriptId, + AnalysisStatus.Finished, + [], + [new AnalysisError("Runner for language 'language' does not exist.")], + date, + date + ); + _dateTimeProvider.UtcNow().Returns(date); + _guidProvider.NewGuid().Returns(analysisId.Value); + _getScript + .Get(scriptId, Arg.Any()) + .Returns( + Task.FromResult>( + new Script( + scriptId, + projectId, + "script", + "path", + "absolute-path", + new ScriptLanguage("language", ".lang"), + [] + ) + ) + ); + _createAnalysis + .Create( + Arg.Is(info => info.MatchAnalysisResult(expectedAnalysisInfo)), + Arg.Any() + ) + .Returns(expectedAnalysisInfo); + _pluginRepository + .GetPlugin(Arg.Any>()) + .Returns((IScriptRunner?)null); + + var analysisResult = await _runAnalysisService.Run(command); + + analysisResult.AssertAnalysisResult(expectedAnalysisInfo); + } + [Fact] public async Task GivenScriptDoesNotExistsError_thenAnalysisIsFailed() {