mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Rework CommandSchemaResolver and move validation there
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CliFx.Attributes;
|
|
||||||
using CliFx.Exceptions;
|
using CliFx.Exceptions;
|
||||||
using CliFx.Internal;
|
using CliFx.Internal;
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
@@ -13,7 +12,7 @@ namespace CliFx
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default implementation of <see cref="ICliApplication"/>.
|
/// Default implementation of <see cref="ICliApplication"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CliApplication : ICliApplication
|
public class CliApplication : ICliApplication
|
||||||
{
|
{
|
||||||
private readonly ApplicationMetadata _metadata;
|
private readonly ApplicationMetadata _metadata;
|
||||||
private readonly ApplicationConfiguration _configuration;
|
private readonly ApplicationConfiguration _configuration;
|
||||||
@@ -43,85 +42,6 @@ namespace CliFx
|
|||||||
_helpTextRenderer = helpTextRenderer.GuardNotNull(nameof(helpTextRenderer));
|
_helpTextRenderer = helpTextRenderer.GuardNotNull(nameof(helpTextRenderer));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IReadOnlyList<string> GetAvailableCommandSchemasValidationErrors(IReadOnlyList<CommandSchema> availableCommandSchemas)
|
|
||||||
{
|
|
||||||
var result = new List<string>();
|
|
||||||
|
|
||||||
// Fail if there are no commands defined
|
|
||||||
if (!availableCommandSchemas.Any())
|
|
||||||
{
|
|
||||||
result.Add("There are no commands defined.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if there are commands that don't implement ICommand
|
|
||||||
var nonImplementedCommandNames = availableCommandSchemas
|
|
||||||
.Where(c => !c.Type.Implements(typeof(ICommand)))
|
|
||||||
.Select(c => c.Name)
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var commandName in nonImplementedCommandNames)
|
|
||||||
{
|
|
||||||
result.Add(!commandName.IsNullOrWhiteSpace()
|
|
||||||
? $"Command [{commandName}] doesn't implement ICommand."
|
|
||||||
: "Default command doesn't implement ICommand.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if there are multiple commands with the same name
|
|
||||||
var nonUniqueCommandNames = availableCommandSchemas
|
|
||||||
.Select(c => c.Name)
|
|
||||||
.GroupBy(i => i, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.Where(g => g.Count() >= 2)
|
|
||||||
.SelectMany(g => g)
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var commandName in nonUniqueCommandNames)
|
|
||||||
{
|
|
||||||
result.Add(!commandName.IsNullOrWhiteSpace()
|
|
||||||
? $"There are multiple commands defined with name [{commandName}]."
|
|
||||||
: "There are multiple default commands defined.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if there are multiple options with the same name inside the same command
|
|
||||||
foreach (var commandSchema in availableCommandSchemas)
|
|
||||||
{
|
|
||||||
var nonUniqueOptionNames = commandSchema.Options
|
|
||||||
.Where(o => !o.Name.IsNullOrWhiteSpace())
|
|
||||||
.Select(o => o.Name)
|
|
||||||
.GroupBy(i => i, StringComparer.OrdinalIgnoreCase)
|
|
||||||
.Where(g => g.Count() >= 2)
|
|
||||||
.SelectMany(g => g)
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var optionName in nonUniqueOptionNames)
|
|
||||||
{
|
|
||||||
result.Add(!commandSchema.Name.IsNullOrWhiteSpace()
|
|
||||||
? $"There are multiple options defined with name [{optionName}] on command [{commandSchema.Name}]."
|
|
||||||
: $"There are multiple options defined with name [{optionName}] on default command.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var nonUniqueOptionShortNames = commandSchema.Options
|
|
||||||
.Where(o => o.ShortName != null)
|
|
||||||
.Select(o => o.ShortName.Value)
|
|
||||||
.GroupBy(i => i)
|
|
||||||
.Where(g => g.Count() >= 2)
|
|
||||||
.SelectMany(g => g)
|
|
||||||
.Distinct()
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
foreach (var optionShortName in nonUniqueOptionShortNames)
|
|
||||||
{
|
|
||||||
result.Add(!commandSchema.Name.IsNullOrWhiteSpace()
|
|
||||||
? $"There are multiple options defined with short name [{optionShortName}] on command [{commandSchema.Name}]."
|
|
||||||
: $"There are multiple options defined with short name [{optionShortName}] on default command.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
||||||
{
|
{
|
||||||
@@ -129,23 +49,17 @@ namespace CliFx
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Get schemas for all available command types
|
||||||
|
var availableCommandSchemas = _commandSchemaResolver.GetCommandSchemas(_configuration.CommandTypes);
|
||||||
|
|
||||||
|
// Parse command input from arguments
|
||||||
var commandInput = _commandInputParser.ParseCommandInput(commandLineArguments);
|
var commandInput = _commandInputParser.ParseCommandInput(commandLineArguments);
|
||||||
|
|
||||||
var availableCommandSchemas = _commandSchemaResolver.GetCommandSchemas(_configuration.CommandTypes);
|
// Find command schema matching the name specified in the input
|
||||||
var matchingCommandSchema = availableCommandSchemas.FindByName(commandInput.CommandName);
|
var targetCommandSchema = availableCommandSchemas.FindByName(commandInput.CommandName);
|
||||||
|
|
||||||
// Validate available command schemas
|
|
||||||
var validationErrors = GetAvailableCommandSchemasValidationErrors(availableCommandSchemas);
|
|
||||||
if (validationErrors.Any())
|
|
||||||
{
|
|
||||||
foreach (var error in validationErrors)
|
|
||||||
_console.WithForegroundColor(ConsoleColor.Red, () => _console.Output.WriteLine(error));
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle cases where requested command is not defined
|
// Handle cases where requested command is not defined
|
||||||
if (matchingCommandSchema == null)
|
if (targetCommandSchema == null)
|
||||||
{
|
{
|
||||||
var isError = false;
|
var isError = false;
|
||||||
|
|
||||||
@@ -164,7 +78,7 @@ namespace CliFx
|
|||||||
// Use a stub if parent command schema is not found
|
// Use a stub if parent command schema is not found
|
||||||
if (parentCommandSchema == null)
|
if (parentCommandSchema == null)
|
||||||
{
|
{
|
||||||
parentCommandSchema = _commandSchemaResolver.GetCommandSchema(typeof(StubDefaultCommand));
|
parentCommandSchema = CommandSchema.StubDefaultCommand;
|
||||||
availableCommandSchemas = availableCommandSchemas.Concat(new[] { parentCommandSchema }).ToArray();
|
availableCommandSchemas = availableCommandSchemas.Concat(new[] { parentCommandSchema }).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,18 +100,19 @@ namespace CliFx
|
|||||||
// Show help if it was requested
|
// Show help if it was requested
|
||||||
if (commandInput.IsHelpRequested())
|
if (commandInput.IsHelpRequested())
|
||||||
{
|
{
|
||||||
var helpTextSource = new HelpTextSource(_metadata, availableCommandSchemas, matchingCommandSchema);
|
var helpTextSource = new HelpTextSource(_metadata, availableCommandSchemas, targetCommandSchema);
|
||||||
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an instance of the command
|
// Create an instance of the command
|
||||||
var command = _commandFactory.CreateCommand(matchingCommandSchema.Type);
|
var command = _commandFactory.CreateCommand(targetCommandSchema.Type);
|
||||||
|
|
||||||
// Populate command with options according to its schema
|
// Populate command with options according to its schema
|
||||||
_commandInitializer.InitializeCommand(command, matchingCommandSchema, commandInput);
|
_commandInitializer.InitializeCommand(command, targetCommandSchema, commandInput);
|
||||||
|
|
||||||
|
// Execute command
|
||||||
await command.ExecuteAsync(_console);
|
await command.ExecuteAsync(_console);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -221,13 +136,4 @@ namespace CliFx
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class CliApplication
|
|
||||||
{
|
|
||||||
[Command]
|
|
||||||
private sealed class StubDefaultCommand : ICommand
|
|
||||||
{
|
|
||||||
public Task ExecuteAsync(IConsole console) => Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
26
CliFx/Exceptions/InvalidCommandSchemaException.cs
Normal file
26
CliFx/Exceptions/InvalidCommandSchemaException.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CliFx.Exceptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Thrown when a command schema fails validation.
|
||||||
|
/// </summary>
|
||||||
|
public class InvalidCommandSchemaException : CliFxException
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="InvalidCommandSchemaException"/>.
|
||||||
|
/// </summary>
|
||||||
|
public InvalidCommandSchemaException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="InvalidCommandSchemaException"/>.
|
||||||
|
/// </summary>
|
||||||
|
public InvalidCommandSchemaException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,10 +74,10 @@ namespace CliFx.Models
|
|||||||
// We define them here to serve as a single source of truth, because they are used...
|
// We define them here to serve as a single source of truth, because they are used...
|
||||||
// ...in CliApplication (when reading) and HelpTextRenderer (when writing).
|
// ...in CliApplication (when reading) and HelpTextRenderer (when writing).
|
||||||
|
|
||||||
internal static CommandOptionSchema Help { get; } =
|
internal static CommandOptionSchema HelpOption { get; } =
|
||||||
new CommandOptionSchema(null, "help", 'h', false, "Shows help text.");
|
new CommandOptionSchema(null, "help", 'h', false, "Shows help text.");
|
||||||
|
|
||||||
internal static CommandOptionSchema Version { get; } =
|
internal static CommandOptionSchema VersionOption { get; } =
|
||||||
new CommandOptionSchema(null, "version", null, false, "Shows version information.");
|
new CommandOptionSchema(null, "version", null, false, "Shows version information.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace CliFx.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Schema of a defined command.
|
/// Schema of a defined command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CommandSchema
|
public partial class CommandSchema
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Underlying type.
|
/// Underlying type.
|
||||||
@@ -60,4 +60,10 @@ namespace CliFx.Models
|
|||||||
return buffer.ToString();
|
return buffer.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public partial class CommandSchema
|
||||||
|
{
|
||||||
|
internal static CommandSchema StubDefaultCommand { get; } =
|
||||||
|
new CommandSchema(null, null, null, new CommandOptionSchema[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ namespace CliFx.Models
|
|||||||
|
|
||||||
var firstOption = commandInput.Options.FirstOrDefault();
|
var firstOption = commandInput.Options.FirstOrDefault();
|
||||||
|
|
||||||
return firstOption != null && CommandOptionSchema.Help.MatchesAlias(firstOption.Alias);
|
return firstOption != null && CommandOptionSchema.HelpOption.MatchesAlias(firstOption.Alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -123,7 +123,7 @@ namespace CliFx.Models
|
|||||||
|
|
||||||
var firstOption = commandInput.Options.FirstOrDefault();
|
var firstOption = commandInput.Options.FirstOrDefault();
|
||||||
|
|
||||||
return firstOption != null && CommandOptionSchema.Version.MatchesAlias(firstOption.Alias);
|
return firstOption != null && CommandOptionSchema.VersionOption.MatchesAlias(firstOption.Alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
|
using CliFx.Exceptions;
|
||||||
using CliFx.Internal;
|
using CliFx.Internal;
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
|
|
||||||
@@ -26,11 +28,8 @@ namespace CliFx.Services
|
|||||||
attribute.Description);
|
attribute.Description);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
private CommandSchema GetCommandSchema(Type commandType)
|
||||||
public CommandSchema GetCommandSchema(Type commandType)
|
|
||||||
{
|
{
|
||||||
commandType.GuardNotNull(nameof(commandType));
|
|
||||||
|
|
||||||
// Attribute is optional for commands in order to reduce runtime rule complexity
|
// Attribute is optional for commands in order to reduce runtime rule complexity
|
||||||
var attribute = commandType.GetCustomAttribute<CommandAttribute>();
|
var attribute = commandType.GetCustomAttribute<CommandAttribute>();
|
||||||
|
|
||||||
@@ -41,5 +40,88 @@ namespace CliFx.Services
|
|||||||
attribute?.Description,
|
attribute?.Description,
|
||||||
options);
|
options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<CommandSchema> GetCommandSchemas(IReadOnlyList<Type> commandTypes)
|
||||||
|
{
|
||||||
|
commandTypes.GuardNotNull(nameof(commandTypes));
|
||||||
|
|
||||||
|
// Get command schemas
|
||||||
|
var commandSchemas = commandTypes.Select(GetCommandSchema).ToArray();
|
||||||
|
|
||||||
|
// Throw if there are no commands defined
|
||||||
|
if (!commandSchemas.Any())
|
||||||
|
{
|
||||||
|
throw new InvalidCommandSchemaException("There are no commands defined.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw if there are multiple commands with the same name
|
||||||
|
var nonUniqueCommandNames = commandSchemas
|
||||||
|
.Select(c => c.Name)
|
||||||
|
.GroupBy(i => i, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Where(g => g.Count() >= 2)
|
||||||
|
.SelectMany(g => g)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var commandName in nonUniqueCommandNames)
|
||||||
|
{
|
||||||
|
throw new InvalidCommandSchemaException(!commandName.IsNullOrWhiteSpace()
|
||||||
|
? $"There are multiple commands defined with name [{commandName}]."
|
||||||
|
: "There are multiple default commands defined.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw if there are commands that don't implement ICommand
|
||||||
|
var nonImplementedCommandNames = commandSchemas
|
||||||
|
.Where(c => !c.Type.Implements(typeof(ICommand)))
|
||||||
|
.Select(c => c.Name)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var commandName in nonImplementedCommandNames)
|
||||||
|
{
|
||||||
|
throw new InvalidCommandSchemaException(!commandName.IsNullOrWhiteSpace()
|
||||||
|
? $"Command [{commandName}] doesn't implement ICommand."
|
||||||
|
: "Default command doesn't implement ICommand.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw if there are multiple options with the same name inside the same command
|
||||||
|
foreach (var commandSchema in commandSchemas)
|
||||||
|
{
|
||||||
|
var nonUniqueOptionNames = commandSchema.Options
|
||||||
|
.Where(o => !o.Name.IsNullOrWhiteSpace())
|
||||||
|
.Select(o => o.Name)
|
||||||
|
.GroupBy(i => i, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Where(g => g.Count() >= 2)
|
||||||
|
.SelectMany(g => g)
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var optionName in nonUniqueOptionNames)
|
||||||
|
{
|
||||||
|
throw new InvalidCommandSchemaException(!commandSchema.Name.IsNullOrWhiteSpace()
|
||||||
|
? $"There are multiple options defined with name [{optionName}] on command [{commandSchema.Name}]."
|
||||||
|
: $"There are multiple options defined with name [{optionName}] on default command.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonUniqueOptionShortNames = commandSchema.Options
|
||||||
|
.Where(o => o.ShortName != null)
|
||||||
|
.Select(o => o.ShortName.Value)
|
||||||
|
.GroupBy(i => i)
|
||||||
|
.Where(g => g.Count() >= 2)
|
||||||
|
.SelectMany(g => g)
|
||||||
|
.Distinct()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var optionShortName in nonUniqueOptionShortNames)
|
||||||
|
{
|
||||||
|
throw new InvalidCommandSchemaException(!commandSchema.Name.IsNullOrWhiteSpace()
|
||||||
|
? $"There are multiple options defined with short name [{optionShortName}] on command [{commandSchema.Name}]."
|
||||||
|
: $"There are multiple options defined with short name [{optionShortName}] on default command.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commandSchemas;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CliFx.Internal;
|
using CliFx.Internal;
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
@@ -12,15 +11,14 @@ namespace CliFx.Services
|
|||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves command schemas for commands of specified types.
|
/// Resolves command schema for specified command type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IReadOnlyList<CommandSchema> GetCommandSchemas(this ICommandSchemaResolver resolver,
|
public static CommandSchema GetCommandSchema(this ICommandSchemaResolver resolver, Type commandType)
|
||||||
IReadOnlyList<Type> commandTypes)
|
|
||||||
{
|
{
|
||||||
resolver.GuardNotNull(nameof(resolver));
|
resolver.GuardNotNull(nameof(resolver));
|
||||||
commandTypes.GuardNotNull(nameof(commandTypes));
|
commandType.GuardNotNull(nameof(commandType));
|
||||||
|
|
||||||
return commandTypes.Select(resolver.GetCommandSchema).ToArray();
|
return resolver.GetCommandSchemas(new[] {commandType}).First();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ namespace CliFx.Services
|
|||||||
var row = 0;
|
var row = 0;
|
||||||
|
|
||||||
// Get built-in option schemas (help and version)
|
// Get built-in option schemas (help and version)
|
||||||
var builtInOptionSchemas = new List<CommandOptionSchema> { CommandOptionSchema.Help };
|
var builtInOptionSchemas = new List<CommandOptionSchema> {CommandOptionSchema.HelpOption};
|
||||||
if (source.TargetCommandSchema.IsDefault())
|
if (source.TargetCommandSchema.IsDefault())
|
||||||
builtInOptionSchemas.Add(CommandOptionSchema.Version);
|
builtInOptionSchemas.Add(CommandOptionSchema.VersionOption);
|
||||||
|
|
||||||
// Get child command schemas
|
// Get child command schemas
|
||||||
var childCommandSchemas = source.AvailableCommandSchemas
|
var childCommandSchemas = source.AvailableCommandSchemas
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using CliFx.Models;
|
using CliFx.Models;
|
||||||
|
|
||||||
namespace CliFx.Services
|
namespace CliFx.Services
|
||||||
@@ -9,8 +10,8 @@ namespace CliFx.Services
|
|||||||
public interface ICommandSchemaResolver
|
public interface ICommandSchemaResolver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves schema of a command of specified type.
|
/// Resolves schemas of specified command types.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
CommandSchema GetCommandSchema(Type commandType);
|
IReadOnlyList<CommandSchema> GetCommandSchemas(IReadOnlyList<Type> commandTypes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user