Files
ShrinkCommand/Runtime/Core/ShrinkCommandService.cs
T
cneicy efe3a22a06
Publish UPM package / publish (push) Failing after 1s
chore: initialize standalone UPM package
2026-08-26 02:49:36 +08:00

885 lines
34 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
namespace ShrinkCommand
{
public sealed class ShrinkCommandService
{
private sealed class CommandDefinition
{
public ShrinkCommandDescriptor Descriptor { get; set; } = new();
public IReadOnlyList<string> ArgumentNames { get; set; } = Array.Empty<string>();
public IReadOnlyList<PathPattern> Patterns { get; set; } = Array.Empty<PathPattern>();
public Func<ShrinkCommandContext, UniTask<ShrinkCommandExecutionResult>> Handler { get; set; } =
_ => UniTask.FromResult(ShrinkCommandExecutionResult.Success());
}
private sealed class PathPattern
{
public string Text { get; set; } = string.Empty;
public IReadOnlyList<PathSegment> Segments { get; set; } = Array.Empty<PathSegment>();
}
private sealed class PathSegment
{
public bool IsArgument { get; set; }
public bool IsGreedy { get; set; }
public string Token { get; set; } = string.Empty;
}
private sealed class MatchCandidate
{
public CommandDefinition Definition { get; set; } = null!;
public PathPattern Pattern { get; set; } = null!;
public IReadOnlyList<string> RawArgumentValues { get; set; } = Array.Empty<string>();
public int LiteralScore { get; set; }
}
private readonly List<CommandDefinition> _commands = new();
private readonly Dictionary<Type, Func<string, object?>> _converters = new();
public event Action<ShrinkCommandExecutingInfo>? OnCommandExecuting;
public event Action<ShrinkCommandExecutedInfo>? OnCommandExecuted;
public ShrinkCommandService()
{
RegisterDefaultConverters();
RegisterBuiltInCommands();
}
public IReadOnlyList<ShrinkCommandDescriptor> Commands => _commands
.Select(command => command.Descriptor)
.OrderBy(command => command.Path, StringComparer.OrdinalIgnoreCase)
.ToArray();
public void RegisterConverter<T>(Func<string, T> converter)
{
if (converter == null)
throw new ArgumentNullException(nameof(converter));
_converters[typeof(T)] = raw => converter(raw);
}
public void RegisterCommand(ShrinkCommandRegistration registration)
{
if (registration == null)
throw new ArgumentNullException(nameof(registration));
if (registration.Handler == null)
throw new ArgumentNullException(nameof(registration.Handler));
var primaryPattern = ParsePattern(registration.Path);
var argumentNames = primaryPattern.Segments
.Where(segment => segment.IsArgument)
.Select(segment => segment.Token)
.ToArray();
var patterns = new List<PathPattern> { primaryPattern };
foreach (var alias in registration.Aliases ?? Array.Empty<string>())
{
if (string.IsNullOrWhiteSpace(alias))
continue;
var aliasPattern = ParsePattern(alias);
ValidateAliasShape(primaryPattern, aliasPattern);
patterns.Add(aliasPattern);
}
var descriptor = new ShrinkCommandDescriptor
{
Path = primaryPattern.Text,
Aliases = (registration.Aliases ?? Array.Empty<string>())
.Where(alias => !string.IsNullOrWhiteSpace(alias))
.Select(alias => alias.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray(),
Description = registration.Description?.Trim() ?? string.Empty,
Permission = registration.Permission?.Trim() ?? string.Empty,
SourceKind = registration.SourceKind,
Hidden = registration.Hidden
};
var duplicate = _commands.FirstOrDefault(existing =>
existing.Patterns.Any(existingPattern =>
patterns.Any(newPattern => string.Equals(existingPattern.Text, newPattern.Text, StringComparison.OrdinalIgnoreCase))));
if (duplicate != null)
throw new InvalidOperationException($"Command path already registered: {duplicate.Descriptor.Path}");
_commands.Add(new CommandDefinition
{
Descriptor = descriptor,
ArgumentNames = argumentNames,
Patterns = patterns,
Handler = registration.Handler
});
}
public void AutoRegisterStaticCommands()
{
ShrinkCommandRegHelper.RegisterStaticCommands(this);
}
public void RegisterCommands(object target)
{
ShrinkCommandRegHelper.RegisterCommands(this, target);
}
public void AutoRegisterAll()
{
AutoRegisterStaticCommands();
}
public async UniTask<ShrinkCommandExecutionResult> ExecuteAsync(
IShrinkCommandSource source,
string rawInput,
CancellationToken cancellationToken = default)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
var input = (rawInput ?? string.Empty).Trim().TrimStart('\uFEFF');
if (string.IsNullOrWhiteSpace(input))
return ShrinkCommandExecutionResult.Success(BuildHelp(source));
List<string> tokens;
try
{
tokens = Tokenize(input);
}
catch (Exception ex)
{
return CompleteEarly(source, input, null, new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase),
ShrinkCommandExecutionResult.Failure("命令解析失败: " + ex.Message));
}
var matches = FindMatches(tokens);
if (matches.Count == 0)
{
var suggestions = BuildSuggestions(tokens, source);
return CompleteEarly(source, input, null, new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase),
string.IsNullOrWhiteSpace(suggestions)
? ShrinkCommandExecutionResult.Failure($"未知命令: {input}\n输入 `help` 查看可用命令。")
: ShrinkCommandExecutionResult.Failure($"未知命令: {input}\n{suggestions}"));
}
var selected = SelectBestMatch(matches);
if (!CanRunSourceKind(selected.Definition.Descriptor.SourceKind, source))
{
return CompleteEarly(source, input, selected.Definition.Descriptor,
BuildArgumentMap(selected.Definition.ArgumentNames, selected.RawArgumentValues),
ShrinkCommandExecutionResult.Failure("该命令当前来源不可执行。"));
}
if (!string.IsNullOrWhiteSpace(selected.Definition.Descriptor.Permission) &&
!source.HasPermission(selected.Definition.Descriptor.Permission))
{
return CompleteEarly(source, input, selected.Definition.Descriptor,
BuildArgumentMap(selected.Definition.ArgumentNames, selected.RawArgumentValues),
ShrinkCommandExecutionResult.Failure(
$"没有权限执行该命令: {selected.Definition.Descriptor.Permission}"));
}
var argumentMap = BuildArgumentMap(selected.Definition.ArgumentNames, selected.RawArgumentValues);
var context = new ShrinkCommandContext(this, source, selected.Definition.Descriptor, input, argumentMap,
cancellationToken);
var executingInfo = new ShrinkCommandExecutingInfo
{
Service = this,
Source = source,
Command = selected.Definition.Descriptor,
RawInput = input,
Arguments = argumentMap
};
try
{
OnCommandExecuting?.Invoke(executingInfo);
var result = await selected.Definition.Handler(context);
OnCommandExecuted?.Invoke(new ShrinkCommandExecutedInfo
{
Service = this,
Source = source,
Command = selected.Definition.Descriptor,
RawInput = input,
Arguments = argumentMap,
Result = result
});
return result;
}
catch (Exception ex)
{
ShrinkCommandLogger.Exception(ex);
var failure = ShrinkCommandExecutionResult.Failure("命令执行失败: " + ex.Message);
OnCommandExecuted?.Invoke(new ShrinkCommandExecutedInfo
{
Service = this,
Source = source,
Command = selected.Definition.Descriptor,
RawInput = input,
Arguments = argumentMap,
Result = failure
});
return failure;
}
}
public string BuildHelp(IShrinkCommandSource? source, string? prefix = null)
{
var entries = Commands
.Where(command => !command.Hidden)
.Where(command => source == null || CanRunSourceKind(command.SourceKind, source))
.Where(command =>
{
if (string.IsNullOrWhiteSpace(command.Permission) || source == null)
return true;
return source.HasPermission(command.Permission);
})
.Where(command =>
string.IsNullOrWhiteSpace(prefix) ||
command.Path.StartsWith(prefix.Trim(), StringComparison.OrdinalIgnoreCase) ||
command.Aliases.Any(alias => alias.StartsWith(prefix.Trim(), StringComparison.OrdinalIgnoreCase)))
.OrderBy(command => command.Path, StringComparer.OrdinalIgnoreCase)
.ToArray();
if (entries.Length == 0)
return "没有找到可用命令。";
var builder = new StringBuilder();
builder.AppendLine("可用命令:");
foreach (var command in entries)
{
builder.Append("- ");
builder.Append(command.Path);
if (!string.IsNullOrWhiteSpace(command.Description))
{
builder.Append(" : ");
builder.Append(command.Description);
}
if (command.Aliases.Count > 0)
{
builder.Append(" [别名: ");
builder.Append(string.Join(", ", command.Aliases));
builder.Append(']');
}
if (!string.IsNullOrWhiteSpace(command.Permission))
{
builder.Append(" [权限: ");
builder.Append(command.Permission);
builder.Append(']');
}
builder.AppendLine();
}
return builder.ToString().TrimEnd();
}
internal object? ConvertArgument(Type targetType, string raw)
{
if (targetType == typeof(string))
return raw;
var underlyingType = Nullable.GetUnderlyingType(targetType);
if (underlyingType != null)
{
if (string.IsNullOrWhiteSpace(raw))
return null;
return ConvertArgument(underlyingType, raw);
}
if (targetType.IsEnum)
return Enum.Parse(targetType, raw, true);
if (_converters.TryGetValue(targetType, out var converter))
return converter(raw);
throw new InvalidOperationException($"Unsupported command argument type: {targetType.FullName}");
}
private void RegisterBuiltInCommands()
{
RegisterCommand(new ShrinkCommandRegistration
{
Path = "help",
Aliases = new[] { "?" },
Description = "显示所有可用命令。",
Handler = context => UniTask.FromResult(
ShrinkCommandExecutionResult.Success(BuildHelp(context.Source)))
});
RegisterCommand(new ShrinkCommandRegistration
{
Path = "help <path...>",
Aliases = new[] { "? <path...>" },
Description = "显示某个前缀下的可用命令。",
Hidden = true,
Handler = context =>
{
var prefix = context.GetArgument("path");
return UniTask.FromResult(
ShrinkCommandExecutionResult.Success(BuildHelp(context.Source, prefix)));
}
});
}
private ShrinkCommandExecutionResult CompleteEarly(
IShrinkCommandSource source,
string rawInput,
ShrinkCommandDescriptor? command,
IReadOnlyDictionary<string, string> arguments,
ShrinkCommandExecutionResult result)
{
OnCommandExecuted?.Invoke(new ShrinkCommandExecutedInfo
{
Service = this,
Source = source,
Command = command,
RawInput = rawInput,
Arguments = arguments,
Result = result
});
return result;
}
private static bool CanRunSourceKind(ShrinkCommandSourceKind sourceKind, IShrinkCommandSource source)
{
switch (sourceKind)
{
case ShrinkCommandSourceKind.ConsoleOnly:
return source.IsConsole;
case ShrinkCommandSourceKind.NonConsoleOnly:
return !source.IsConsole;
default:
return true;
}
}
private static IReadOnlyDictionary<string, string> BuildArgumentMap(
IReadOnlyList<string> argumentNames,
IReadOnlyList<string> values)
{
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
for (var index = 0; index < argumentNames.Count && index < values.Count; index++)
map[argumentNames[index]] = values[index];
return map;
}
private List<MatchCandidate> FindMatches(IReadOnlyList<string> tokens)
{
var matches = new List<MatchCandidate>();
foreach (var command in _commands)
{
foreach (var pattern in command.Patterns)
{
if (!TryMatchPattern(pattern, tokens, out var arguments, out var literalScore))
continue;
matches.Add(new MatchCandidate
{
Definition = command,
Pattern = pattern,
RawArgumentValues = arguments,
LiteralScore = literalScore
});
}
}
return matches;
}
private MatchCandidate SelectBestMatch(IReadOnlyList<MatchCandidate> matches)
{
var selected = matches
.OrderByDescending(match => match.Pattern.Segments.Count)
.ThenByDescending(match => match.LiteralScore)
.ThenBy(match => match.Definition.Descriptor.Path, StringComparer.OrdinalIgnoreCase)
.First();
var ambiguous = matches
.Where(match => !ReferenceEquals(match, selected))
.FirstOrDefault(match =>
match.Pattern.Segments.Count == selected.Pattern.Segments.Count &&
match.LiteralScore == selected.LiteralScore);
if (ambiguous != null)
{
throw new InvalidOperationException(
$"命令匹配不明确: {selected.Pattern.Text} / {ambiguous.Pattern.Text}");
}
return selected;
}
private string BuildSuggestions(IReadOnlyList<string> tokens, IShrinkCommandSource source)
{
var entries = Commands
.Where(command => !command.Hidden)
.Where(command => CanRunSourceKind(command.SourceKind, source))
.Where(command =>
string.IsNullOrWhiteSpace(command.Permission) || source.HasPermission(command.Permission))
.Where(command => IsPrefixCandidate(command.Path, tokens) || command.Aliases.Any(alias => IsPrefixCandidate(alias, tokens)))
.OrderBy(command => command.Path, StringComparer.OrdinalIgnoreCase)
.Take(8)
.ToArray();
if (entries.Length == 0)
return string.Empty;
var builder = new StringBuilder();
builder.AppendLine("你可能想输入:");
foreach (var entry in entries)
{
builder.Append("- ");
builder.Append(entry.Path);
if (!string.IsNullOrWhiteSpace(entry.Description))
{
builder.Append(" : ");
builder.Append(entry.Description);
}
builder.AppendLine();
}
return builder.ToString().TrimEnd();
}
private static bool IsPrefixCandidate(string path, IReadOnlyList<string> inputTokens)
{
if (inputTokens.Count == 0)
return true;
var pathTokens = path.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (pathTokens.Length < inputTokens.Count)
return false;
for (var index = 0; index < inputTokens.Count; index++)
{
var input = inputTokens[index];
var pathToken = pathTokens[index];
if (pathToken.StartsWith("<", StringComparison.Ordinal) && pathToken.EndsWith(">", StringComparison.Ordinal))
continue;
if (!pathToken.StartsWith(input, StringComparison.OrdinalIgnoreCase))
return false;
}
return true;
}
private static bool TryMatchPattern(
PathPattern pattern,
IReadOnlyList<string> tokens,
out IReadOnlyList<string> arguments,
out int literalScore)
{
var rawArguments = new List<string>();
literalScore = 0;
arguments = rawArguments;
var tokenIndex = 0;
for (var segmentIndex = 0; segmentIndex < pattern.Segments.Count; segmentIndex++)
{
var segment = pattern.Segments[segmentIndex];
if (segment.IsArgument)
{
if (segment.IsGreedy)
{
if (tokenIndex >= tokens.Count)
return false;
rawArguments.Add(string.Join(" ", tokens.Skip(tokenIndex)));
tokenIndex = tokens.Count;
continue;
}
if (tokenIndex >= tokens.Count)
return false;
rawArguments.Add(tokens[tokenIndex]);
tokenIndex++;
continue;
}
if (tokenIndex >= tokens.Count)
return false;
if (!string.Equals(segment.Token, tokens[tokenIndex], StringComparison.OrdinalIgnoreCase))
return false;
literalScore++;
tokenIndex++;
}
return tokenIndex == tokens.Count;
}
private static void ValidateAliasShape(PathPattern primaryPattern, PathPattern aliasPattern)
{
if (primaryPattern.Segments.Count != aliasPattern.Segments.Count)
{
throw new InvalidOperationException(
$"Alias path shape mismatch: {aliasPattern.Text} vs {primaryPattern.Text}");
}
for (var index = 0; index < primaryPattern.Segments.Count; index++)
{
var primary = primaryPattern.Segments[index];
var alias = aliasPattern.Segments[index];
if (primary.IsArgument != alias.IsArgument || primary.IsGreedy != alias.IsGreedy)
{
throw new InvalidOperationException(
$"Alias path shape mismatch: {aliasPattern.Text} vs {primaryPattern.Text}");
}
}
}
private static PathPattern ParsePattern(string path)
{
var normalized = path?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(normalized))
throw new InvalidOperationException("Command path cannot be empty.");
var tokens = normalized.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var segments = new List<PathSegment>(tokens.Length);
for (var index = 0; index < tokens.Length; index++)
{
var token = tokens[index].Trim();
if (token.Length >= 3 && token[0] == '<' && token[token.Length - 1] == '>')
{
var name = token.Substring(1, token.Length - 2).Trim();
var isGreedy = name.EndsWith("...", StringComparison.Ordinal);
if (isGreedy)
{
name = name.Substring(0, name.Length - 3).Trim();
if (index != tokens.Length - 1)
throw new InvalidOperationException($"Greedy argument must be the last segment: {path}");
}
if (string.IsNullOrWhiteSpace(name))
throw new InvalidOperationException($"Invalid argument segment: {token}");
segments.Add(new PathSegment
{
IsArgument = true,
IsGreedy = isGreedy,
Token = name
});
continue;
}
segments.Add(new PathSegment
{
IsArgument = false,
IsGreedy = false,
Token = token
});
}
return new PathPattern
{
Text = normalized,
Segments = segments
};
}
private static List<string> Tokenize(string input)
{
var tokens = new List<string>();
var builder = new StringBuilder();
var inQuote = false;
var quoteChar = '\0';
var escaping = false;
foreach (var character in input)
{
if (escaping)
{
builder.Append(character);
escaping = false;
continue;
}
if (character == '\\')
{
escaping = true;
continue;
}
if (inQuote)
{
if (character == quoteChar)
{
inQuote = false;
continue;
}
builder.Append(character);
continue;
}
if (character == '"' || character == '\'')
{
inQuote = true;
quoteChar = character;
continue;
}
if (char.IsWhiteSpace(character))
{
if (builder.Length <= 0)
continue;
tokens.Add(builder.ToString());
builder.Clear();
continue;
}
builder.Append(character);
}
if (escaping)
builder.Append('\\');
if (inQuote)
throw new InvalidOperationException("存在未闭合的引号。");
if (builder.Length > 0)
tokens.Add(builder.ToString());
return tokens;
}
private void RegisterDefaultConverters()
{
RegisterConverter<int>(raw => int.Parse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture));
RegisterConverter<long>(raw => long.Parse(raw, NumberStyles.Integer, CultureInfo.InvariantCulture));
RegisterConverter<float>(raw => float.Parse(raw, NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture));
RegisterConverter<double>(raw => double.Parse(raw, NumberStyles.Float | NumberStyles.AllowThousands,
CultureInfo.InvariantCulture));
RegisterConverter<decimal>(raw => decimal.Parse(raw, NumberStyles.Number, CultureInfo.InvariantCulture));
RegisterConverter<Guid>(raw => Guid.Parse(raw));
RegisterConverter<DateTime>(raw => DateTime.Parse(raw, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind));
RegisterConverter<TimeSpan>(raw => TimeSpan.Parse(raw, CultureInfo.InvariantCulture));
RegisterConverter<bool>(ParseBool);
}
private static bool ParseBool(string raw)
{
if (bool.TryParse(raw, out var value))
return value;
switch (raw.Trim().ToLowerInvariant())
{
case "1":
case "yes":
case "on":
return true;
case "0":
case "no":
case "off":
return false;
default:
throw new FormatException($"无法把 `{raw}` 解析为布尔值。");
}
}
}
internal static class ShrinkCommandMethodAdapter
{
private static readonly MethodInfo AwaitUniTaskGenericMethod =
typeof(ShrinkCommandMethodAdapter).GetMethod(nameof(AwaitUniTaskGeneric),
BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly MethodInfo AwaitTaskGenericMethod =
typeof(ShrinkCommandMethodAdapter).GetMethod(nameof(AwaitTaskGeneric),
BindingFlags.NonPublic | BindingFlags.Static)!;
public static Func<ShrinkCommandContext, UniTask<ShrinkCommandExecutionResult>> BuildHandler(
object? target,
MethodInfo method,
IReadOnlyList<string> argumentNames)
{
if (method == null)
throw new ArgumentNullException(nameof(method));
var parameterDescriptors = method.GetParameters();
var specialCount = 0;
foreach (var parameter in parameterDescriptors)
{
if (parameter.ParameterType == typeof(ShrinkCommandContext) ||
typeof(IShrinkCommandSource).IsAssignableFrom(parameter.ParameterType))
{
specialCount++;
}
}
if (parameterDescriptors.Length - specialCount != argumentNames.Count)
{
throw new InvalidOperationException(
$"Command parameter count does not match path arguments: {method.DeclaringType?.FullName}.{method.Name}");
}
ValidateReturnType(method);
return async context =>
{
var invokeArguments = new object?[parameterDescriptors.Length];
var rawArgumentIndex = 0;
for (var index = 0; index < parameterDescriptors.Length; index++)
{
var parameter = parameterDescriptors[index];
if (parameter.ParameterType == typeof(ShrinkCommandContext))
{
invokeArguments[index] = context;
continue;
}
if (typeof(IShrinkCommandSource).IsAssignableFrom(parameter.ParameterType))
{
if (!parameter.ParameterType.IsInstanceOfType(context.Source))
{
throw new InvalidOperationException(
$"Command source type mismatch. Required={parameter.ParameterType.FullName}, Actual={context.Source.GetType().FullName}");
}
invokeArguments[index] = context.Source;
continue;
}
var argumentName = argumentNames[rawArgumentIndex];
var rawValue = context.GetArgument(argumentName);
try
{
invokeArguments[index] = context.Service.ConvertArgument(parameter.ParameterType, rawValue);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"参数 `{argumentName}` 解析失败: {ex.Message}", ex);
}
rawArgumentIndex++;
}
object? returnValue;
try
{
returnValue = method.Invoke(target, invokeArguments);
}
catch (TargetInvocationException ex)
{
throw ex.InnerException ?? ex;
}
return await NormalizeReturnAsync(method.ReturnType, returnValue);
};
}
private static void ValidateReturnType(MethodInfo method)
{
var returnType = method.ReturnType;
if (returnType == typeof(void) ||
returnType == typeof(string) ||
returnType == typeof(ShrinkCommandExecutionResult) ||
returnType == typeof(UniTask) ||
returnType == typeof(Task))
{
return;
}
if (returnType.IsGenericType)
{
var genericTypeDefinition = returnType.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(UniTask<>) ||
genericTypeDefinition == typeof(Task<>))
{
var innerType = returnType.GetGenericArguments()[0];
if (innerType == typeof(string) || innerType == typeof(ShrinkCommandExecutionResult))
return;
}
}
throw new InvalidOperationException(
$"Unsupported command return type: {method.DeclaringType?.FullName}.{method.Name}");
}
private static async UniTask<ShrinkCommandExecutionResult> NormalizeReturnAsync(Type returnType, object? returnValue)
{
if (returnType == typeof(void))
return ShrinkCommandExecutionResult.Success();
if (returnType == typeof(string))
return WrapObject(returnValue);
if (returnType == typeof(ShrinkCommandExecutionResult))
return WrapObject(returnValue);
if (returnType == typeof(UniTask))
{
await (UniTask)(returnValue ?? throw new InvalidOperationException("Command returned null UniTask."));
return ShrinkCommandExecutionResult.Success();
}
if (returnType == typeof(Task))
{
await (Task)(returnValue ?? throw new InvalidOperationException("Command returned null Task."));
return ShrinkCommandExecutionResult.Success();
}
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(UniTask<>))
{
return await (UniTask<ShrinkCommandExecutionResult>)AwaitUniTaskGenericMethod
.MakeGenericMethod(returnType.GetGenericArguments()[0])
.Invoke(null, new[] { returnValue! })!;
}
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
return await (Task<ShrinkCommandExecutionResult>)AwaitTaskGenericMethod
.MakeGenericMethod(returnType.GetGenericArguments()[0])
.Invoke(null, new[] { returnValue! })!;
}
throw new InvalidOperationException($"Unsupported command return type: {returnType.FullName}");
}
private static async UniTask<ShrinkCommandExecutionResult> AwaitUniTaskGeneric<T>(UniTask<T> task)
{
var result = await task;
return WrapObject(result);
}
private static async Task<ShrinkCommandExecutionResult> AwaitTaskGeneric<T>(Task<T> task)
{
var result = await task;
return WrapObject(result);
}
private static ShrinkCommandExecutionResult WrapObject(object? value)
{
if (value == null)
return ShrinkCommandExecutionResult.Success();
if (value is ShrinkCommandExecutionResult commandResult)
return commandResult;
if (value is string text)
return ShrinkCommandExecutionResult.Success(text);
throw new InvalidOperationException(
$"Unsupported command result payload type: {value.GetType().FullName}");
}
}
}