604 lines
21 KiB
C#
604 lines
21 KiB
C#
using System.Data.Common;
|
|
using System.Reflection;
|
|
using System.Runtime.Loader;
|
|
using SmartB2B.Enova.Contracts;
|
|
using SmartB2B.Enova.Service;
|
|
using SmartB2B.Enova.Service.Configuration;
|
|
using SmartB2B.Enova.Service.Orders;
|
|
using SmartB2B.Enova.Service.Rpc;
|
|
using SmartB2B.Enova.Service.Runtime;
|
|
using SmartB2B.Enova.Service.Sql;
|
|
using WampSharp.Core.Serialization;
|
|
using WampSharp.V2.Core;
|
|
using WampSharp.V2.Core.Contracts;
|
|
using WampSharp.V2.Rpc;
|
|
|
|
var tests = new (string Name, Func<Task> Run)[]
|
|
{
|
|
("Poprawne zamówienie z rabatem ułamkowym", ValidFractionalDiscount),
|
|
("Rabat 100 procent jest dozwolony", FullDiscount),
|
|
("Rabat ujemny jest odrzucany", NegativeDiscount),
|
|
("Rabat powyżej 100 jest odrzucany", DiscountAboveOneHundred),
|
|
("Ilość zerowa jest odrzucana", ZeroQuantity),
|
|
("Cena ujemna jest odrzucana", NegativePrice),
|
|
("Brak towaru jest odrzucany", MissingProduct),
|
|
("Brak pozycji jest odrzucany", MissingItems),
|
|
("Powtarzająca się referencja nie jest walidowana", ReferenceMayRepeat),
|
|
("Kontrakt place_order jest mapowany na Enovę", PlaceOrderMapping),
|
|
("place_order zwraca nazwany wynik", PlaceOrderKeywordResult),
|
|
("Warstwa WAMP wysyła wynik jako kwargs", WampKeywordWireResult),
|
|
("Brak waluty zwraca stabilny błąd", MissingCurrency),
|
|
("Waluta inna niż PLN jest odrzucana", UnsupportedCurrency),
|
|
("Błąd adaptera jest wypisywany na stderr", AdapterErrorIsWrittenToStderr),
|
|
("Brak katalogu Enovy jest wykrywany", MissingEnovaDirectory),
|
|
("Sekrety są odczytywane z pliku konfiguracji", SecretsComeFromConfigurationFile),
|
|
("Brak danych operatora wyłącza place_order", MissingCredentialsDisablePlaceOrder),
|
|
("Częściowe dane operatora wyłączają place_order", PartialCredentialsDisablePlaceOrder),
|
|
("Parametr /debug włącza szczegółowe logowanie", DebugSwitchEnablesVerboseLogging),
|
|
("Parametr /verbose można wyłączyć", VerboseSwitchCanBeDisabled),
|
|
("Tryb szczegółowy jest przekazywany do adaptera", VerboseModeReachesAdapter),
|
|
("Rejestracja bazy przenosi parametry connection stringa", DatabaseRegistrationUsesConnectionString),
|
|
("Provider SQL działa na bieżącej platformie", SqlProviderIsSupported)
|
|
};
|
|
|
|
var failures = 0;
|
|
foreach (var test in tests)
|
|
{
|
|
try
|
|
{
|
|
await test.Run();
|
|
Console.WriteLine($"PASS: {test.Name}");
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
failures++;
|
|
Console.Error.WriteLine($"FAIL: {test.Name}: {exception.Message}");
|
|
}
|
|
}
|
|
|
|
Console.WriteLine($"Wynik: {tests.Length - failures}/{tests.Length} testów zaliczonych.");
|
|
return failures == 0 ? 0 : 1;
|
|
|
|
static Task SqlProviderIsSupported()
|
|
{
|
|
var handler = new SqlRawHandler(
|
|
"Server=localhost;Database=master;Integrated Security=true;TrustServerCertificate=true",
|
|
30);
|
|
var createConnection = typeof(SqlRawHandler).GetMethod(
|
|
"CreateConnection",
|
|
BindingFlags.Instance | BindingFlags.NonPublic)
|
|
?? throw new InvalidOperationException("Nie znaleziono metody tworzącej połączenie SQL.");
|
|
|
|
using var connection = (DbConnection)(createConnection.Invoke(handler, null)
|
|
?? throw new InvalidOperationException("Provider nie utworzył połączenia SQL."));
|
|
|
|
if (connection.GetType().FullName != "Microsoft.Data.SqlClient.SqlConnection")
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Załadowano nieoczekiwany provider SQL: {connection.GetType().AssemblyQualifiedName}.");
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task DatabaseRegistrationUsesConnectionString()
|
|
{
|
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
|
string? installationPath = null;
|
|
while (directory is not null)
|
|
{
|
|
var candidate = Path.Combine(directory.FullName, "enova365 2604.4.4");
|
|
if (File.Exists(Path.Combine(candidate, "Soneta.Business.dll")))
|
|
{
|
|
installationPath = candidate;
|
|
break;
|
|
}
|
|
|
|
directory = directory.Parent;
|
|
}
|
|
|
|
if (installationPath is null)
|
|
{
|
|
throw new InvalidOperationException("Nie znaleziono lokalnej instalacji enova365 do testu adaptera.");
|
|
}
|
|
|
|
Assembly? ResolveSonetaAssembly(AssemblyLoadContext context, AssemblyName name)
|
|
{
|
|
var candidate = Path.Combine(installationPath, $"{name.Name}.dll");
|
|
return File.Exists(candidate) ? context.LoadFromAssemblyPath(candidate) : null;
|
|
}
|
|
|
|
AssemblyLoadContext.Default.Resolving += ResolveSonetaAssembly;
|
|
var adapterAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(
|
|
Path.Combine(AppContext.BaseDirectory, "SmartB2B.Enova.Adapter.dll"));
|
|
var adapterType = adapterAssembly.GetType("SmartB2B.Enova.Adapter.EnovaOrderAdapter", throwOnError: true)
|
|
?? throw new InvalidOperationException("Nie znaleziono typu adaptera Enovy.");
|
|
var parseConnectionString = adapterType.GetMethod(
|
|
"ParseSqlConnectionString",
|
|
BindingFlags.Static | BindingFlags.NonPublic)
|
|
?? throw new InvalidOperationException("Nie znaleziono metody analizującej connection string Enovy.");
|
|
object settings;
|
|
try
|
|
{
|
|
settings = parseConnectionString.Invoke(
|
|
null,
|
|
["Server=sql01;Database=Moms_Care;User ID=smartb2b;Password=haslo-sql;TrustServerCertificate=True"])
|
|
?? throw new InvalidOperationException("Adapter nie odczytał konfiguracji połączenia Enovy.");
|
|
}
|
|
catch (TargetInvocationException exception) when (exception.InnerException is not null)
|
|
{
|
|
throw exception.InnerException;
|
|
}
|
|
|
|
AssertEqual("sql01", GetProperty(settings, "DataSource"), "serwer SQL");
|
|
AssertEqual("Moms_Care", GetProperty(settings, "InitialCatalog"), "nazwa bazy SQL");
|
|
AssertEqual("smartb2b", GetProperty(settings, "UserID"), "użytkownik SQL");
|
|
AssertEqual("haslo-sql", GetProperty(settings, "Password"), "hasło SQL");
|
|
AssertEqual(false, GetProperty(settings, "IntegratedSecurity"), "uwierzytelnianie zintegrowane");
|
|
AssertEqual(true, GetProperty(settings, "TrustServerCertificate"), "zaufanie certyfikatowi SQL");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static object? GetProperty(object instance, string name) =>
|
|
instance.GetType().GetProperty(name)?.GetValue(instance)
|
|
?? throw new InvalidOperationException($"Nie znaleziono wartości właściwości '{name}'.");
|
|
|
|
static Task ValidFractionalDiscount()
|
|
{
|
|
AssertNoErrors(CreateValidRequest(12.5m));
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task FullDiscount()
|
|
{
|
|
AssertNoErrors(CreateValidRequest(100m));
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task NegativeDiscount()
|
|
{
|
|
AssertHasError(CreateValidRequest(-0.01m), "discount");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task DiscountAboveOneHundred()
|
|
{
|
|
AssertHasError(CreateValidRequest(100.01m), "discount");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task ZeroQuantity()
|
|
{
|
|
AssertHasError(CreateValidRequest(quantity: 0m), "quantity");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task NegativePrice()
|
|
{
|
|
AssertHasError(CreateValidRequest(unitPrice: -0.01m), "price_netto");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task MissingProduct()
|
|
{
|
|
AssertHasError(CreateValidRequest(productCode: ""), "symbol");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task MissingItems()
|
|
{
|
|
var request = new OrderRequest { CustomerCode = "Abc", Items = [] };
|
|
AssertHasError(request, "co najmniej jedną");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task ReferenceMayRepeat()
|
|
{
|
|
AssertNoErrors(CreateValidRequest());
|
|
AssertNoErrors(CreateValidRequest());
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static async Task PlaceOrderMapping()
|
|
{
|
|
var adapter = new FakeAdapter();
|
|
var handler = new PlaceOrderHandler(adapter, CreateConfiguration());
|
|
var invocation = CreateInvocation();
|
|
|
|
await handler.HandleAsync(invocation, CancellationToken.None);
|
|
|
|
var request = adapter.LastRequest ?? throw new InvalidOperationException("Adapter nie otrzymał zamówienia.");
|
|
AssertEqual("ABC", request.CustomerCode, "companyErpId");
|
|
AssertEqual("MAG", request.WarehouseCode, "warehouseErpId");
|
|
AssertEqual("PO/1", request.CustomerReference, "purchase_order_number");
|
|
AssertEqual(2, request.Items.Count, "liczba pozycji");
|
|
AssertEqual(12.5m, request.Items[0].Discount, "rabat");
|
|
AssertEqual(120m, request.Items[0].UnitPrice, "cena netto");
|
|
}
|
|
|
|
static async Task PlaceOrderKeywordResult()
|
|
{
|
|
var handler = new PlaceOrderHandler(new FakeAdapter(), CreateConfiguration());
|
|
var result = await handler.HandleAsync(CreateInvocation(), CancellationToken.None);
|
|
var keywords = result as KeywordResult
|
|
?? throw new InvalidOperationException("Wynik nie jest wynikiem nazwanym WAMP.");
|
|
|
|
AssertEqual("ZO/1", keywords.Values["order_erp_symbol"], "order_erp_symbol");
|
|
AssertEqual(100m, keywords.Values["value_netto"], "value_netto");
|
|
AssertEqual(123m, keywords.Values["value_brutto"], "value_brutto");
|
|
}
|
|
|
|
static async Task WampKeywordWireResult()
|
|
{
|
|
var operation = new DelegateRpcOperation(
|
|
"test.keyword",
|
|
(_, _) => Task.FromResult<object?>(new KeywordResult(
|
|
new Dictionary<string, object?> { ["answer"] = 42 })));
|
|
var callback = new FakeRouterCallback();
|
|
|
|
_ = operation.Invoke(
|
|
callback,
|
|
WampObjectFormatter.Value,
|
|
new InvocationDetails(),
|
|
Array.Empty<object>(),
|
|
new Dictionary<string, object>());
|
|
|
|
var result = await callback.Completion.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
|
AssertEqual(0, result.Arguments.Length, "liczba argumentów pozycyjnych WAMP");
|
|
AssertEqual(42, result.KeywordArguments["answer"], "argument nazwany WAMP");
|
|
}
|
|
|
|
static async Task MissingCurrency()
|
|
{
|
|
var handler = new PlaceOrderHandler(new FakeAdapter(), CreateConfiguration());
|
|
var invocation = new RpcInvocation([], new Dictionary<string, object?>());
|
|
await AssertWampError(handler.HandleAsync(invocation, CancellationToken.None), "eu.smartb2b.missing_currency");
|
|
}
|
|
|
|
static async Task UnsupportedCurrency()
|
|
{
|
|
var handler = new PlaceOrderHandler(new FakeAdapter(), CreateConfiguration());
|
|
var invocation = new RpcInvocation([], new Dictionary<string, object?> { ["currency_iso"] = "EUR" });
|
|
await AssertWampError(
|
|
handler.HandleAsync(invocation, CancellationToken.None),
|
|
"eu.smartb2b.erp.currency_not_supported");
|
|
}
|
|
|
|
static async Task AdapterErrorIsWrittenToStderr()
|
|
{
|
|
var previousError = Console.Error;
|
|
using var error = new StringWriter();
|
|
try
|
|
{
|
|
Console.SetError(error);
|
|
var handler = new PlaceOrderHandler(new FailingAdapter(), CreateConfiguration());
|
|
await AssertWampError(
|
|
handler.HandleAsync(CreateInvocation(), CancellationToken.None),
|
|
"eu.smartb2b.company_not_found");
|
|
}
|
|
finally
|
|
{
|
|
Console.SetError(previousError);
|
|
}
|
|
|
|
if (!error.ToString().Contains("Nie znaleziono kontrahenta", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException($"Brak komunikatu błędu na stderr: {error}");
|
|
}
|
|
}
|
|
|
|
static Task MissingEnovaDirectory()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));
|
|
try
|
|
{
|
|
_ = new EnovaAdapterLoader(path, Path.Combine(path, "adapter.dll"));
|
|
throw new InvalidOperationException("Oczekiwano DirectoryNotFoundException.");
|
|
}
|
|
catch (DirectoryNotFoundException)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
|
|
static Task SecretsComeFromConfigurationFile()
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"enova-settings-{Guid.NewGuid():N}.json");
|
|
try
|
|
{
|
|
File.WriteAllText(path, """
|
|
{
|
|
"portal": "demo",
|
|
"wamp": {
|
|
"serverUrl": "ws://localhost:8080/",
|
|
"reconnectDelaySeconds": 10
|
|
},
|
|
"enova": {
|
|
"installationPath": "C:/Enova",
|
|
"database": "Firma demo",
|
|
"operator": "Administrator",
|
|
"password": "haslo-z-pliku",
|
|
"documentDefinition": "ZO",
|
|
"saveMode": "Buffer"
|
|
},
|
|
"sql": {
|
|
"connectionString": "Server=db;Database=enova;User ID=user;Password=sql-z-pliku",
|
|
"commandTimeoutSeconds": 30
|
|
},
|
|
"sentry": {
|
|
"dsn": "https://public@example.com/1"
|
|
},
|
|
"diagnostics": {
|
|
"logDirectory": "C:/logs",
|
|
"logPrefix": "test"
|
|
}
|
|
}
|
|
""");
|
|
|
|
var settings = ServiceSettings.Load(path);
|
|
var runtime = settings.ResolveRuntimeSettings();
|
|
|
|
AssertEqual("https://public@example.com/1", settings.Sentry.Dsn, "DSN Sentry");
|
|
AssertEqual(
|
|
"haslo-z-pliku",
|
|
runtime.EnovaConfiguration?.Connection.Password,
|
|
"hasło operatora");
|
|
AssertEqual(
|
|
"Server=db;Database=enova;User ID=user;Password=sql-z-pliku",
|
|
runtime.SqlConnectionString,
|
|
"connection string SQL");
|
|
AssertEqual(
|
|
runtime.SqlConnectionString,
|
|
runtime.EnovaConfiguration?.Connection.SqlConnectionString,
|
|
"connection string rejestracji bazy Enovy");
|
|
return Task.CompletedTask;
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
|
|
static Task MissingCredentialsDisablePlaceOrder()
|
|
{
|
|
var runtime = LoadRuntimeSettings(string.Empty);
|
|
AssertEqual(null, runtime.EnovaConfiguration, "konfiguracja endpointu place_order");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task PartialCredentialsDisablePlaceOrder()
|
|
{
|
|
var runtime = LoadRuntimeSettings("\"operator\": \"Administrator\",");
|
|
AssertEqual(null, runtime.EnovaConfiguration, "konfiguracja endpointu place_order");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task DebugSwitchEnablesVerboseLogging()
|
|
{
|
|
var options = CommandLineOptions.Parse(["/debug"]);
|
|
AssertEqual(true, options.VerboseLogging, "tryb szczegółowy");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task VerboseSwitchCanBeDisabled()
|
|
{
|
|
var options = CommandLineOptions.Parse(["/verbose", "/noverbose"]);
|
|
AssertEqual(false, options.VerboseLogging, "wyłączony tryb szczegółowy");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task VerboseModeReachesAdapter()
|
|
{
|
|
var runtime = LoadRuntimeSettings(
|
|
"\"operator\": \"Administrator\", \"password\": \"haslo\",",
|
|
verboseLogging: true);
|
|
AssertEqual(true, runtime.EnovaConfiguration?.VerboseLogging, "tryb adaptera");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static RuntimeSettings LoadRuntimeSettings(string credentialsJson, bool verboseLogging = false)
|
|
{
|
|
var path = Path.Combine(Path.GetTempPath(), $"enova-settings-{Guid.NewGuid():N}.json");
|
|
try
|
|
{
|
|
File.WriteAllText(path, $$"""
|
|
{
|
|
"portal": "demo",
|
|
"wamp": { "serverUrl": "ws://localhost:8080/" },
|
|
"enova": {
|
|
"installationPath": "C:/Enova",
|
|
"database": "Firma demo",
|
|
{{credentialsJson}}
|
|
"documentDefinition": "ZO"
|
|
},
|
|
"sql": { "connectionString": "Server=db;Database=enova;User ID=user;Password=secret" }
|
|
}
|
|
""");
|
|
|
|
return ServiceSettings.Load(path).ResolveRuntimeSettings(verboseLogging);
|
|
}
|
|
finally
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
|
|
static RpcInvocation CreateInvocation() => new(
|
|
[],
|
|
new Dictionary<string, object?>
|
|
{
|
|
["currency_iso"] = "PLN",
|
|
["companyErpId"] = "ABC",
|
|
["warehouseErpId"] = "MAG",
|
|
["purchase_order_number"] = "PO/1",
|
|
["notes"] = "Test",
|
|
["lines"] = new[]
|
|
{
|
|
new PlaceOrderLine { Symbol = "T1", Quantity = 2m, PriceNetto = 120m, Discount = 12.5m },
|
|
new PlaceOrderLine { Symbol = "T2", Quantity = 1m, PriceNetto = 50m, Discount = 0m }
|
|
}
|
|
});
|
|
|
|
static EnovaAdapterConfiguration CreateConfiguration() => new(
|
|
new EnovaConnectionOptions(
|
|
"Firma demo",
|
|
"Administrator",
|
|
"",
|
|
"Server=localhost;Database=enova;Integrated Security=true;TrustServerCertificate=true"),
|
|
"ZO",
|
|
null,
|
|
OrderSaveMode.Buffer);
|
|
|
|
static OrderRequest CreateValidRequest(
|
|
decimal discount = 0m,
|
|
decimal quantity = 1m,
|
|
decimal unitPrice = 100m,
|
|
string productCode = "BIKINI") =>
|
|
new()
|
|
{
|
|
CustomerCode = "Abc",
|
|
CustomerReference = "KLIENT/TEST/1",
|
|
Notes = "Test",
|
|
DocumentDefinition = "ZO",
|
|
SaveMode = OrderSaveMode.Buffer,
|
|
Items =
|
|
[
|
|
new OrderLineRequest
|
|
{
|
|
ProductCode = productCode,
|
|
Quantity = quantity,
|
|
UnitPrice = unitPrice,
|
|
Discount = discount
|
|
}
|
|
]
|
|
};
|
|
|
|
static void AssertNoErrors(OrderRequest request)
|
|
{
|
|
var errors = OrderValidator.Validate(request);
|
|
if (errors.Count > 0)
|
|
{
|
|
throw new InvalidOperationException(string.Join(" | ", errors));
|
|
}
|
|
}
|
|
|
|
static void AssertHasError(OrderRequest request, string expectedText)
|
|
{
|
|
var errors = OrderValidator.Validate(request);
|
|
if (!errors.Any(error => error.Contains(expectedText, StringComparison.OrdinalIgnoreCase)))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Oczekiwano błędu zawierającego '{expectedText}', otrzymano: {string.Join(" | ", errors)}");
|
|
}
|
|
}
|
|
|
|
static async Task AssertWampError(Task<object?> task, string expectedUri)
|
|
{
|
|
try
|
|
{
|
|
await task;
|
|
throw new InvalidOperationException($"Oczekiwano błędu {expectedUri}.");
|
|
}
|
|
catch (WampException exception) when (exception.ErrorUri == expectedUri)
|
|
{
|
|
// Oczekiwany błąd.
|
|
}
|
|
}
|
|
|
|
static void AssertEqual(object? expected, object? actual, string name)
|
|
{
|
|
if (!Equals(expected, actual))
|
|
{
|
|
throw new InvalidOperationException($"{name}: oczekiwano '{expected}', otrzymano '{actual}'.");
|
|
}
|
|
}
|
|
|
|
file sealed class FakeAdapter : IEnovaOrderAdapter
|
|
{
|
|
public OrderRequest? LastRequest { get; private set; }
|
|
|
|
public EnovaAdapterInfo Initialize(EnovaAdapterConfiguration configuration) =>
|
|
new("1.0.0", "2604.4.4.0", "2604.4.4.0", configuration.Connection.DatabaseName);
|
|
|
|
public OrderResult CreateOrder(OrderRequest request)
|
|
{
|
|
LastRequest = request;
|
|
return new OrderResult(
|
|
1,
|
|
"ZO/1",
|
|
request.SaveMode,
|
|
request.CustomerCode,
|
|
request.WarehouseCode ?? "FIRMA",
|
|
request.CustomerReference,
|
|
request.Items.Count,
|
|
100m,
|
|
23m,
|
|
123m,
|
|
request.Items.Any(line => line.Discount > 0));
|
|
}
|
|
}
|
|
|
|
file sealed class FailingAdapter : IEnovaOrderAdapter
|
|
{
|
|
public EnovaAdapterInfo Initialize(EnovaAdapterConfiguration configuration) =>
|
|
new("1.0.0", "2604.4.4.0", "2604.4.4.0", configuration.Connection.DatabaseName);
|
|
|
|
public OrderResult CreateOrder(OrderRequest request) =>
|
|
throw new EnovaOperationException(
|
|
"eu.smartb2b.company_not_found",
|
|
$"Nie znaleziono kontrahenta o kodzie '{request.CustomerCode}'.");
|
|
}
|
|
|
|
file sealed class FakeRouterCallback : IWampRawRpcOperationRouterCallback
|
|
{
|
|
public long RequestId => 1;
|
|
|
|
public TaskCompletionSource<CapturedResult> Completion { get; } =
|
|
new(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
|
|
public void Result<TMessage>(IWampFormatter<TMessage> formatter, YieldOptions details) =>
|
|
Completion.TrySetResult(new CapturedResult([], new Dictionary<string, object?>()));
|
|
|
|
public void Result<TMessage>(
|
|
IWampFormatter<TMessage> formatter,
|
|
YieldOptions details,
|
|
TMessage[] arguments) =>
|
|
Completion.TrySetResult(new CapturedResult(
|
|
arguments.Select(formatter.Deserialize<object>).ToArray(),
|
|
new Dictionary<string, object?>()));
|
|
|
|
public void Result<TMessage>(
|
|
IWampFormatter<TMessage> formatter,
|
|
YieldOptions details,
|
|
TMessage[] arguments,
|
|
IDictionary<string, TMessage> argumentsKeywords) =>
|
|
Completion.TrySetResult(new CapturedResult(
|
|
arguments.Select(formatter.Deserialize<object>).ToArray(),
|
|
argumentsKeywords.ToDictionary(
|
|
pair => pair.Key,
|
|
pair => (object?)formatter.Deserialize<object>(pair.Value))));
|
|
|
|
public void Error<TMessage>(IWampFormatter<TMessage> formatter, TMessage details, string error) =>
|
|
Completion.TrySetException(new InvalidOperationException(error));
|
|
|
|
public void Error<TMessage>(
|
|
IWampFormatter<TMessage> formatter,
|
|
TMessage details,
|
|
string error,
|
|
TMessage[] arguments) =>
|
|
Completion.TrySetException(new InvalidOperationException(error));
|
|
|
|
public void Error<TMessage>(
|
|
IWampFormatter<TMessage> formatter,
|
|
TMessage details,
|
|
string error,
|
|
TMessage[] arguments,
|
|
TMessage argumentsKeywords) =>
|
|
Completion.TrySetException(new InvalidOperationException(error));
|
|
}
|
|
|
|
file sealed record CapturedResult(
|
|
object?[] Arguments,
|
|
IReadOnlyDictionary<string, object?> KeywordArguments);
|