diff --git a/.editorconfig b/.editorconfig index ea605d28..e44c44eb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,3 +2,9 @@ # IDE1006: Naming Styles dotnet_diagnostic.IDE1006.severity = silent + +# IDE0290: Use primary constructor +dotnet_diagnostic.IDE0290.severity = silent + +# IDE0028: Simplify collection initialization +dotnet_diagnostic.IDE0028.severity = silent diff --git a/README.md b/README.md index 07d5a48f..386306e8 100644 --- a/README.md +++ b/README.md @@ -425,11 +425,11 @@ var serverBuilder = new ServerBuilder(serverSettings) .WithTransactionalRfc(new MyTransactionRfcHandler()) // MyTransactionRfcHandler has to implement interface - // ITransactionalRfcHandler + // ITransactionalRfcHandler -public interface ITransactionalRfcHandler +public interface ITransactionalRfcHandler { - RfcRc OnCheck(IRfcRuntime rfcRuntime, + Eff OnCheck( IRfcHandle rfcHandle, string transactionId); ... diff --git a/YaNco.sln.DotSettings b/YaNco.sln.DotSettings index 0a14a84d..541e2c42 100644 --- a/YaNco.sln.DotSettings +++ b/YaNco.sln.DotSettings @@ -8,6 +8,7 @@ True True True + True True True True @@ -16,4 +17,5 @@ True True True + True True \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/ConsoleRuntime.cs b/samples/net6.0/ExportMATMAS/ConsoleRuntime.cs new file mode 100644 index 00000000..7f9ad4c6 --- /dev/null +++ b/samples/net6.0/ExportMATMAS/ConsoleRuntime.cs @@ -0,0 +1,106 @@ +using System.Text; +using Dbosoft.YaNco; +using Dbosoft.YaNco.Live; +using Dbosoft.YaNco.Traits; +using Dbosoft.YaNco.TypeMapping; +using ExportMATMAS.MaterialMaster; +using LanguageExt; +using LanguageExt.Sys.Traits; + +namespace ExportMATMAS; + +/// +/// Runtime environment for the console application +/// It provides access to the SAP RFC runtime environment, the console and file IO +/// It is expected that you create your own runtime environment for your application. +/// +public readonly struct ConsoleRuntime : + HasConsole, + HasFile, + HasSAPRfc, + HasSAPRfcServer, + HasMaterialManager +{ + + private readonly SAPRfcRuntimeEnv _env; + + /// + /// Constructor + /// + private ConsoleRuntime(SAPRfcRuntimeEnv env) => + _env = env; + + + /// + /// Configuration environment accessor + /// + public SAPRfcRuntimeEnv Env => + _env ?? throw new InvalidOperationException("Runtime Env not set. Perhaps because of using default(Runtime) or new Runtime() rather than Runtime.New()"); + + SAPRfcRuntimeEnv IHasEnvRuntimeSettings.Env => + Env.ToRuntimeSettings(); + + + /// + /// Constructor function + /// + public static ConsoleRuntime New(CancellationTokenSource cancellationTokenSource, SAPServerSettings settings) => + new(new SAPRfcRuntimeEnv(cancellationTokenSource, settings)); + + + /// + /// Create a new Runtime with a fresh cancellation token + /// + /// Used by localCancel to create new cancellation context for its sub-environment + /// New runtime + public ConsoleRuntime LocalCancel => + new(new SAPRfcRuntimeEnv(new CancellationTokenSource(), Env.Settings)); + + /// + /// Direct access to cancellation token + /// + public CancellationToken CancellationToken => + Env.Token; + + /// + /// Directly access the cancellation token source + /// + /// CancellationTokenSource + public CancellationTokenSource CancellationTokenSource => + Env.Source; + + public Eff ConsoleEff => + Prelude.SuccessEff(LanguageExt.Sys.Live.ConsoleIO.Default); + + public Encoding Encoding => Encoding.UTF8; + + public Eff FileEff => Prelude.SuccessEff(LanguageExt.Sys.Live.FileIO.Default); + + public Option RfcLogger => Env.Settings.Logger == null ? Option.None : Prelude.Some(Env.Settings.Logger); + + private SAPRfcDataIO DataIO => Env.Settings.RfcDataIO ?? new LiveSAPRfcDataIO(RfcLogger, Env.Settings.FieldMapper, Env.Settings.Options); + private SAPRfcFunctionIO FunctionIO => Env.Settings.RfcFunctionIO ?? new LiveSAPRfcFunctionIO(RfcLogger, DataIO); + private SAPRfcConnectionIO ConnectionIO => Env.Settings.RfcConnectionIO ?? new LiveSAPRfcConnectionIO(RfcLogger); + private SAPRfcServerIO ServerIO => Env.Settings.RfcServerIO ?? new LiveSAPRfcServerIO(RfcLogger); + + public Eff> RfcLoggerEff => Prelude.Eff>(rt => rt.RfcLogger); + + public Eff RfcDataEff => Prelude.Eff(rt => rt.DataIO); + + + public Eff RfcFunctionsEff => + Prelude.Eff(rt => rt.FunctionIO); + + public Eff FieldMapperEff => + Prelude.Eff(rt => rt.Env.Settings.FieldMapper); + + public Eff RfcConnectionEff => Prelude.Eff( + rt => rt.ConnectionIO); + + public Eff RfcServerEff => Prelude.Eff( + rt => rt.ServerIO); + + public Eff> MaterialManagerEff => + Prelude.Eff>(rt => + rt.Env.Settings.TaManager); +} \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/ExportMATMAS.csproj b/samples/net6.0/ExportMATMAS/ExportMATMAS.csproj index 802c88e7..ce993751 100644 --- a/samples/net6.0/ExportMATMAS/ExportMATMAS.csproj +++ b/samples/net6.0/ExportMATMAS/ExportMATMAS.csproj @@ -15,12 +15,12 @@ + - - + diff --git a/samples/net6.0/ExportMATMAS/HasMaterialManager.cs b/samples/net6.0/ExportMATMAS/HasMaterialManager.cs new file mode 100644 index 00000000..96d4fb0c --- /dev/null +++ b/samples/net6.0/ExportMATMAS/HasMaterialManager.cs @@ -0,0 +1,11 @@ +using ExportMATMAS.MaterialMaster; +using LanguageExt; +using LanguageExt.Effects.Traits; + +namespace ExportMATMAS; + +public interface HasMaterialManager : HasCancel + where RT : struct, HasCancel +{ + Eff> MaterialManagerEff { get; } +} \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/ClientData.cs b/samples/net6.0/ExportMATMAS/MaterialMaster/ClientData.cs similarity index 50% rename from samples/net6.0/ExportMATMAS/ClientData.cs rename to samples/net6.0/ExportMATMAS/MaterialMaster/ClientData.cs index 770f415f..6e9a3167 100644 --- a/samples/net6.0/ExportMATMAS/ClientData.cs +++ b/samples/net6.0/ExportMATMAS/MaterialMaster/ClientData.cs @@ -1,3 +1,3 @@ -namespace ExportMATMAS; +namespace ExportMATMAS.MaterialMaster; public record ClientData(string Unit); \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/DescriptionData.cs b/samples/net6.0/ExportMATMAS/MaterialMaster/DescriptionData.cs similarity index 63% rename from samples/net6.0/ExportMATMAS/DescriptionData.cs rename to samples/net6.0/ExportMATMAS/MaterialMaster/DescriptionData.cs index 43d71f60..23aab458 100644 --- a/samples/net6.0/ExportMATMAS/DescriptionData.cs +++ b/samples/net6.0/ExportMATMAS/MaterialMaster/DescriptionData.cs @@ -1,3 +1,3 @@ -namespace ExportMATMAS; +namespace ExportMATMAS.MaterialMaster; public record DescriptionData(string Language, string Description); \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/MaterialMasterRecord.cs b/samples/net6.0/ExportMATMAS/MaterialMaster/MaterialMasterRecord.cs similarity index 77% rename from samples/net6.0/ExportMATMAS/MaterialMasterRecord.cs rename to samples/net6.0/ExportMATMAS/MaterialMaster/MaterialMasterRecord.cs index eb4b5e55..c9a893b5 100644 --- a/samples/net6.0/ExportMATMAS/MaterialMasterRecord.cs +++ b/samples/net6.0/ExportMATMAS/MaterialMaster/MaterialMasterRecord.cs @@ -1,3 +1,3 @@ -namespace ExportMATMAS; +namespace ExportMATMAS.MaterialMaster; public record MaterialMasterRecord(string MaterialNo, ClientData ClientData, DescriptionData[] Descriptions, PlantData[] PlantData); \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/MaterialMaster/MaterialMasterTransactionalRfcHandler.cs b/samples/net6.0/ExportMATMAS/MaterialMaster/MaterialMasterTransactionalRfcHandler.cs new file mode 100644 index 00000000..11792869 --- /dev/null +++ b/samples/net6.0/ExportMATMAS/MaterialMaster/MaterialMasterTransactionalRfcHandler.cs @@ -0,0 +1,100 @@ +using Dbosoft.YaNco; +using System.Text.Encodings.Web; +using System.Text.Json; +using LanguageExt; +using LanguageExt.Sys; +using LanguageExt.Sys.Traits; + +namespace ExportMATMAS.MaterialMaster; + + +/// +/// This is a sample implementation of a transactional RFC handler. +/// +public class MaterialMasterTransactionalRfcHandler : ITransactionalRfcHandler + where RT : struct, HasConsole, HasMaterialManager +{ + + public Eff OnCheck(IRfcHandle rfcHandle, string transactionId) => + from uLog in Console.writeLine($"Checking transaction '{transactionId}'") + from tm in default(RT).MaterialManagerEff + let result = tm.GetTransaction(transactionId).Match( + None: () => + { + tm.AddTransaction(transactionId); + return RfcRc.RFC_OK; + }, + Some: ta => + { + //check if TA is in valid state, inform sender in case TA is already executed + return ta.State switch + { + TransactionState.Created => RfcRc.RFC_OK, + TransactionState.Executed => RfcRc.RFC_EXECUTED, + TransactionState.Committed => RfcRc.RFC_EXECUTED, + TransactionState.RolledBack => RfcRc.RFC_OK, + _ => throw new ArgumentOutOfRangeException() + }; + }) + select result; + + public Eff OnCommit(IRfcHandle rfcHandle, string transactionId) => + from uLog in Console.writeLine($"Commit transaction '{transactionId}'") + from tm in default(RT).MaterialManagerEff + from ta in tm.GetTransaction(transactionId) + .ToEff(RfcError.Error($"Transaction {transactionId} not found", RfcRc.RFC_EXTERNAL_FAILURE)) + + from gNotExecuted in Prelude.guard(ta.State == TransactionState.Executed, + RfcError.Error($"Transaction {transactionId} not executed", RfcRc.RFC_EXTERNAL_FAILURE).AsError) + from gHasData in Prelude.guardnot(ta.Data == null, RfcError.Error($"Transaction {transactionId} has no data", RfcRc.RFC_EXTERNAL_FAILURE).AsError) + + + // where you would normally commit the transaction, but here we just print the data + from printData in PrettyPrintMaterial(ta.Data) + from uPrint in Console.writeLine(printData) + + from rc in Prelude.Eff(() => + { + ta.State = TransactionState.Committed; + return RfcRc.RFC_OK; + }) + select rc; + + public Eff OnRollback(IRfcHandle rfcHandle, string transactionId) => + from uLog in Console.writeLine($"Rollback transaction '{transactionId}'") + from tm in default(RT).MaterialManagerEff + let result = tm.GetTransaction(transactionId).Match( + None: () => RfcRc.RFC_EXTERNAL_FAILURE, + Some: ta => + { + ta.State = TransactionState.RolledBack; + return RfcRc.RFC_OK; + }) + select result; + + public Eff OnConfirm(IRfcHandle rfcHandle, string transactionId) => + from uLog in Console.writeLine($"Confirm transaction '{transactionId}'") + from tm in default(RT).MaterialManagerEff + from result in Prelude.Eff(_ => + { + // cleanup transaction data + tm.RemoveTransaction(transactionId); + return RfcRc.RFC_OK; + }) select result; + + private static Eff PrettyPrintMaterial(MaterialMasterRecord record) + { + return Prelude.Eff( () =>JsonSerializer.Serialize(record, Json.JsonOptions)); + } + +} + +internal class Json +{ + public static readonly JsonSerializerOptions JsonOptions = new() + { + //json don't has to be valid, so disable text encoding for unicode chars + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + WriteIndented = true + }; +} \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/MatmasTypes.cs b/samples/net6.0/ExportMATMAS/MaterialMaster/MatmasTypes.cs similarity index 74% rename from samples/net6.0/ExportMATMAS/MatmasTypes.cs rename to samples/net6.0/ExportMATMAS/MaterialMaster/MatmasTypes.cs index f43c65d8..5fe4aead 100644 --- a/samples/net6.0/ExportMATMAS/MatmasTypes.cs +++ b/samples/net6.0/ExportMATMAS/MaterialMaster/MatmasTypes.cs @@ -1,7 +1,10 @@ using LanguageExt; // ReSharper disable StringLiteralTypo -namespace ExportMATMAS; +namespace ExportMATMAS.MaterialMaster; + +// for a known IDoc type you used fixed segment to type mapping +// a more generic way would be looking up segment names from RFM IDOCTYPE_READ_COMPLETE public static class MatmasTypes { diff --git a/samples/net6.0/ExportMATMAS/PlantData.cs b/samples/net6.0/ExportMATMAS/MaterialMaster/PlantData.cs similarity index 61% rename from samples/net6.0/ExportMATMAS/PlantData.cs rename to samples/net6.0/ExportMATMAS/MaterialMaster/PlantData.cs index ebfe8046..9bb59c60 100644 --- a/samples/net6.0/ExportMATMAS/PlantData.cs +++ b/samples/net6.0/ExportMATMAS/MaterialMaster/PlantData.cs @@ -1,3 +1,3 @@ -namespace ExportMATMAS; +namespace ExportMATMAS.MaterialMaster; public record PlantData(string Plant, string PurchasingGroup); \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/MaterialMasterTransactionalRfcHandler.cs b/samples/net6.0/ExportMATMAS/MaterialMasterTransactionalRfcHandler.cs deleted file mode 100644 index 0477170c..00000000 --- a/samples/net6.0/ExportMATMAS/MaterialMasterTransactionalRfcHandler.cs +++ /dev/null @@ -1,112 +0,0 @@ -using Dbosoft.YaNco; -using System.Text.Encodings.Web; -using System.Text.Json; -using Dbosoft.YaNco.Live; - -namespace ExportMATMAS; - - -/// -/// This is a sample implementation of a transactional RFC handler. -/// -public class MaterialMasterTransactionalRfcHandler : ITransactionalRfcHandler -{ - private readonly TransactionManager _transactionManager; - - public MaterialMasterTransactionalRfcHandler(TransactionManager transactionManager) - { - _transactionManager = transactionManager; - } - - public RfcRc OnCheck(SAPRfcRuntime rfcRuntime, IRfcHandle rfcHandle, string transactionId) - { - Console.WriteLine($"Checking transaction '{transactionId}'"); - - try - { - return _transactionManager.GetTransaction(transactionId).Match( - None: () => - { - _transactionManager.AddTransaction(transactionId); - return RfcRc.RFC_OK; - }, - Some: ta => - { - //check if TA is in valid state, inform sender in case TA is already executed - return ta.State switch - { - TransactionState.Created => RfcRc.RFC_OK, - TransactionState.Executed => RfcRc.RFC_EXECUTED, - TransactionState.Committed => RfcRc.RFC_EXECUTED, - TransactionState.RolledBack => RfcRc.RFC_OK, - _ => throw new ArgumentOutOfRangeException() - }; - }); - } - catch (Exception) - { - // in case of a error it is required to return RFC_EXTERNAL_FAILURE - return RfcRc.RFC_EXTERNAL_FAILURE; - - } - - } - - public RfcRc OnCommit(SAPRfcRuntime rfcRuntime, IRfcHandle rfcHandle, string transactionId) - { - Console.WriteLine($"Commit transaction '{transactionId}'"); - - // from here on it is save to process the data - - return _transactionManager.GetTransaction(transactionId).Match( - None: () => RfcRc.RFC_EXTERNAL_FAILURE, - Some: ta => - { - if(ta.State!= TransactionState.Executed) - return RfcRc.RFC_EXTERNAL_FAILURE; - - if(ta.Data== null) - return RfcRc.RFC_EXTERNAL_FAILURE; - - //simple print the data - Console.WriteLine(PrettyPrintMaterial(ta.Data)); - ta.State = TransactionState.Committed; - return RfcRc.RFC_OK; - }); - - } - - public RfcRc OnRollback(SAPRfcRuntime rfcRuntime, IRfcHandle rfcHandle, string transactionId) - { - Console.WriteLine($"Rollback transaction '{transactionId}'"); - - return _transactionManager.GetTransaction(transactionId).Match( - None: () => RfcRc.RFC_EXTERNAL_FAILURE, - Some: ta => - { - ta.State = TransactionState.RolledBack; - return RfcRc.RFC_OK; - }); - } - - public RfcRc OnConfirm(SAPRfcRuntime rfcRuntime, IRfcHandle rfcHandle, string transactionId) - { - Console.WriteLine($"Confirm transaction '{transactionId}'"); - - // cleanup transaction data - _transactionManager.RemoveTransaction(transactionId); - return RfcRc.RFC_OK; - } - - private static string PrettyPrintMaterial(MaterialMasterRecord record) - { - return JsonSerializer.Serialize(record, JsonOptions); - } - - private static readonly JsonSerializerOptions JsonOptions = new() - { - //json don't has to be valid, so disable text encoding for unicode chars - Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - WriteIndented = true - }; -} \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/Program.cs b/samples/net6.0/ExportMATMAS/Program.cs index 4b9875e0..411b2c31 100644 --- a/samples/net6.0/ExportMATMAS/Program.cs +++ b/samples/net6.0/ExportMATMAS/Program.cs @@ -1,9 +1,14 @@ - -using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.CodeAnalysis; +using Dbosoft.YaNco.Live; +using Dbosoft.YaNco; using ExportMATMAS; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using LanguageExt; +using ExportMATMAS.MaterialMaster; +using static Dbosoft.YaNco.SAPRfcServer; +using static ExportMATMAS.SAPIDocServer; +using static LanguageExt.Sys.Console; + // ReSharper disable CommentTypo [assembly: ExcludeFromCodeCoverage] @@ -14,12 +19,74 @@ // - maintained connection settings either in appsettings.json file, or in user secrets // - a outbound idoc configuration in the SAP system that sends MATMAS to destination YANCO_MATMAS -var host = Host.CreateDefaultBuilder() - .ConfigureAppConfiguration(cfg => - cfg.AddEnvironmentVariables("saprfc")) - .ConfigureServices(services => services - .AddHostedService()) - .Build(); +var configurationBuilder = + new ConfigurationBuilder(); + +configurationBuilder.AddJsonFile("appsettings.json", true, false); +configurationBuilder.AddUserSecrets(); + +var configuration = configurationBuilder.Build(); + +// ReSharper disable StringLiteralTypo +var serverSettings = new Dictionary + { + + { "SYSID", configuration["saprfc:sysid"] }, + { "PROGRAM_ID", configuration["saprfc:program_id"] }, + { "GWHOST", configuration["saprfc:ashost"] }, + { "GWSERV", "sapgw" + configuration["saprfc:sysnr"] }, + { "REG_COUNT", "1" }, + { "TRACE", "0" } + }; + +var clientSettings = new Dictionary + { + { "ashost", configuration["saprfc:ashost"] }, + { "sysnr", configuration["saprfc:sysnr"] }, + { "client", configuration["saprfc:client"] }, + { "user", configuration["saprfc:user"] }, + { "passwd", configuration["saprfc:passwd"] }, + { "lang", "EN" } + }; +// ReSharper restore StringLiteralTypo + +// create the runtime that will be used to run the server +var runtime = ConsoleRuntime.New(new CancellationTokenSource(), + new SAPServerSettings(null, SAPRfcRuntime.Default.Env.Settings.FieldMapper, + new RfcRuntimeOptions(), new TransactionManager())); + +// build the server IO effect +var serverIO = + from serverAff in buildServer(serverSettings, + + // IDocs have to be processed with a transactional RFC handler + c => c.WithTransactionalRfc( + new MaterialMasterTransactionalRfcHandler()) + .WithClientConnection(clientSettings, + cc => cc + .WithFunctionHandler("IDOC_INBOUND_ASYNCHRONOUS", processInboundIDoc))) + from _ in useServer(serverAff, rfcServer => + + from uInfo in writeLine( + $""" + MATMAS IDOC Server is ready. + You can now send MATMAS IDocs to the server. + You should also see a registration in the SAP system with the program id {configuration["saprfc:program_id"]}. + + If not, check the RFC trace files in the current directory or + SAP transaction smgw to see if the connection is blocked by + gateway security settings (reginfo). + + >> Press any key to stop the server + """) + from uStopped in readKey + from _ in stopServer(rfcServer) + select uInfo + ) + from uStopped in writeLine("Server stopped") + select _; +// run the IO effect to creates and stops the server with the runtime +var res = await serverIO.Run(runtime); -await host.RunAsync(); \ No newline at end of file +res.IfFail(ex => Console.WriteLine(ex.ToString())); \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/SAPIDocServer.cs b/samples/net6.0/ExportMATMAS/SAPIDocServer.cs index 05bbe9fd..7021e5d3 100644 --- a/samples/net6.0/ExportMATMAS/SAPIDocServer.cs +++ b/samples/net6.0/ExportMATMAS/SAPIDocServer.cs @@ -1,92 +1,146 @@ using Dbosoft.YaNco; -using Dbosoft.YaNco.Live; +using Dbosoft.YaNco.Traits; +using ExportMATMAS.MaterialMaster; using LanguageExt; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Hosting; +using static LanguageExt.Prelude; + +// ReSharper disable InconsistentNaming // ReSharper disable StringLiteralTypo namespace ExportMATMAS; -// ReSharper disable InconsistentNaming -public enum TransactionState +/// +/// Pure functional IDOC server implementation +/// +/// +public static class SAPIDocServer where RT : + struct, HasSAPRfcServer, HasSAPRfc, + HasMaterialManager { - Created, - Executed, - Committed, - RolledBack - -} -// ReSharper restore InconsistentNaming + public static Aff processInboundIDoc(CalledFunction cf) => cf + .Input(i => + + // extract IDoc data from incoming RFC call + from data in i.MapTable("IDOC_DATA_REC_40", + s => + from iDocNo in s.GetField("DOCNUM") + from segment in s.GetField("SEGNAM") + from segmentNo in s.GetField("SEGNUM") + from parentNo in s.GetField("PSGNUM") + from level in s.GetField("HLEVEL") + from data in s.GetField("SDATA") + select new IDocDataRecord(iDocNo, segment, segmentNo, parentNo, level, data) + ) + select data.ToSeq()) + .Process(data => + + from state in SAPRfcServer.getServerAttributes(cf.RfcHandle) + from guardIsTa in guardnot( + state.CallType != RfcCallType.QUEUED && state.CallType != RfcCallType.TRANSACTIONAL, + RfcError.Error($"Invalid call type {state.CallType}", RfcRc.RFC_EXTERNAL_FAILURE).AsError) + from guardTaEmpty in guardnot(string.IsNullOrWhiteSpace(state.TransactionId), + RfcError.Error("Missing transaction id", RfcRc.RFC_EXTERNAL_FAILURE).AsError) + + // open a IRfcContext to call back to sender + from clientCall in cf.UseRfcContext(context => + + // get current transaction + from tm in default(RT).MaterialManagerEff + from ta in tm.GetTransaction(state.TransactionId) + .ToEff(RfcError.Error( "failed to read transaction data", RfcRc.RFC_EXTERNAL_FAILURE)) + + // open a client connection to sender for metadata lookup + from connection in context.GetConnection() + from materialMaster in ExtractMaterialMaster(connection, data).ToAff(l => l) + + from unit in SetTransactionData(ta, materialMaster) + select unit) + select clientCall + ) + .NoReply(); + + + private static Eff SetTransactionData( + TransactionStateRecord ta, MaterialMasterRecord materialMaster) + { + ta.Data = materialMaster; + ta.State = TransactionState.Executed; + return unitEff; + } -public class SAPIDocServer : BackgroundService -{ - private readonly IConfiguration _configuration; - private readonly TransactionManager _transactionManager = new(); - public SAPIDocServer(IConfiguration configuration) + private static EitherAsync ExtractMaterialMaster(IConnection connection, + Seq data) { - _configuration = configuration; + return + + //extract some client data of material master + from clientSegment in FindRequiredSegment("E1MARAM", data) + from material in MapSegment(connection, clientSegment, s => + from materialNo in s.GetField("MATNR") + from unit in s.GetField("MEINS") + select new + { + MaterialNo = materialNo, + ClientData = new ClientData(unit) + }) + + //extract descriptions data of material master + from descriptionData in MapSegments(connection, FindSegments("E1MAKTM", data), s => + from language in s.GetField("SPRAS_ISO") + from description in s.GetField("MAKTX") + select new DescriptionData(language, description)) + + //extract some plant data of material master + from plantData in MapSegments(connection, FindSegments("E1MARCM", data), s => + from plant in s.GetField("WERKS") + from purchasingGroup in s.GetField("EKGRP") + select new PlantData(plant, purchasingGroup) + ) + select new MaterialMasterRecord( + material.MaterialNo, + material.ClientData, + descriptionData.ToArray(), + plantData.ToArray()); } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + private static EitherAsync MapSegment(IConnection connection, + IDocDataRecord data, Func> mapFunc) { - - - var serverSettings = new Dictionary - { - - { "SYSID", _configuration["saprfc:sysid"] }, - { "PROGRAM_ID", _configuration["saprfc:program_id"] }, - { "GWHOST", _configuration["saprfc:ashost"] }, - { "GWSERV", "sapgw" + _configuration["saprfc:sysnr"] }, - { "REG_COUNT", "1" }, - { "TRACE", "0" } - }; - - var clientSettings = new Dictionary - { - { "ashost", _configuration["saprfc:ashost"] }, - { "sysnr", _configuration["saprfc:sysnr"] }, - { "client", _configuration["saprfc:client"] }, - { "user", _configuration["saprfc:user"] }, - { "passwd", _configuration["saprfc:passwd"] }, - { "lang", "EN" } - }; - - - var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken); - - - using var rfcServer = await new ServerBuilder(serverSettings) - .WithTransactionalRfc(new MaterialMasterTransactionalRfcHandler(_transactionManager)) - .WithClientConnection(clientSettings, - c => c - .WithFunctionHandler("IDOC_INBOUND_ASYNCHRONOUS", ProcessInboundIDoc)) - .Build() - .StartOrException(); - - - Console.WriteLine("MATMAS IDOC Server is ready"); - - try - { - await Task.Delay(Timeout.Infinite, cancellationTokenSource.Token); - } - catch (TaskCanceledException) + //to convert the segment we create a temporary structure of the segment definition type + //and "move" the segment data into it. + return connection.CreateStructure(MatmasTypes.Segment2Type[data.Segment]).Use(structure => { - } - - - _ = await rfcServer.Stop().ToEither(); + return from _ in structure.Bind(s => s.SetFromString(data.Data).ToAsync()) + from res in structure.Bind(s => mapFunc(s).ToAsync()) + select res; + }); - Console.WriteLine("Server stopped"); } + private static EitherAsync> MapSegments(IConnection connection, + Seq data, Func> mapFunc) + { + return data.Map(segment => MapSegment(connection, segment, mapFunc)) + .TraverseSerial(l => l); + } - private Aff ProcessInboundIDoc(CalledFunction cf) => - SAPIDocServer.processInboundIDoc(cf, _transactionManager); -} + private static EitherAsync FindRequiredSegment( + string typeName, Seq records) + { + var segmentName = MatmasTypes.Type2Segment[typeName]; + return records.Find(x => x.Segment == segmentName) + .ToEither(RfcError.Error($"Segment {segmentName} not found")) + .ToAsync(); + } + private static Seq FindSegments( + string typeName, Seq records) + { + var segmentName = MatmasTypes.Type2Segment[typeName]; + return records.Filter(x => x.Segment == segmentName); + } -// ReSharper disable InconsistentNaming -// ReSharper restore InconsistentNaming \ No newline at end of file + // for a known IDoc type you used fixed segment to type mapping + // a more generic way would be looking up segment names from RFM IDOCTYPE_READ_COMPLETE +} \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/SAPIDocServerRT.cs b/samples/net6.0/ExportMATMAS/SAPIDocServerRT.cs deleted file mode 100644 index 3979ea3d..00000000 --- a/samples/net6.0/ExportMATMAS/SAPIDocServerRT.cs +++ /dev/null @@ -1,136 +0,0 @@ -using Dbosoft.YaNco; -using LanguageExt; -// ReSharper disable InconsistentNaming -// ReSharper disable StringLiteralTypo - -namespace ExportMATMAS; - -public static class SAPIDocServer where RT : - struct, HasSAPRfcServer, HasSAPRfcFunctions, HasSAPRfcConnection, HasSAPRfcLogger, HasSAPRfcData -{ - public static Aff processInboundIDoc(CalledFunction cf, TransactionManager transactionManager) => cf - .Input(i => - - // extract IDoc data from incoming RFC call - from data in i.MapTable("IDOC_DATA_REC_40", - s => - from iDocNo in s.GetField("DOCNUM") - from segment in s.GetField("SEGNAM") - from segmentNo in s.GetField("SEGNUM") - from parentNo in s.GetField("PSGNUM") - from level in s.GetField("HLEVEL") - from data in s.GetField("SDATA") - select new IDocDataRecord(iDocNo, segment, segmentNo, parentNo, level, data) - ) - select data.ToSeq()) - .Process(data => - - from state in SAPRfcServer.getServerAttributes(cf.RfcHandle) - from guardIsTa in Prelude.guardnot( - state.CallType != RfcCallType.QUEUED && state.CallType != RfcCallType.TRANSACTIONAL, - RfcError.Error($"Invalid call type {state.CallType}", RfcRc.RFC_EXTERNAL_FAILURE).AsError) - from guardTaEmpty in Prelude.guardnot(string.IsNullOrWhiteSpace(state.TransactionId), - RfcError.Error("Missing transaction id", RfcRc.RFC_EXTERNAL_FAILURE).AsError) - - // open a IRfcContext to call back to sender - from clientCall in cf.UseRfcContext(context => - - // get current transaction - from ta in transactionManager.GetTransaction(state.TransactionId) - .ToEither(RfcError.Error(RfcRc.RFC_EXTERNAL_FAILURE)) - .ToEff(l => l) - // open a client connection to sender for metadata lookup - from connection in context.GetConnection() - from materialMaster in ExtractMaterialMaster(connection, data).ToAff(l => l) - - from unit in SetTransactionData(ta, materialMaster) - select unit) - select clientCall - ) - .NoReply(); - - - private static Eff SetTransactionData( - TransactionStateRecord ta, MaterialMasterRecord materialMaster) - { - ta.Data = materialMaster; - ta.State = TransactionState.Executed; - return Prelude.unitEff; - } - - - private static EitherAsync ExtractMaterialMaster(IConnection connection, - Seq data) - { - return - - //extract some client data of material master - from clientSegment in FindRequiredSegment("E1MARAM", data) - from material in MapSegment(connection, clientSegment, s => - from materialNo in s.GetField("MATNR") - from unit in s.GetField("MEINS") - select new - { - MaterialNo = materialNo, - ClientData = new ClientData(unit) - }) - - //extract descriptions data of material master - from descriptionData in MapSegments(connection, FindSegments("E1MAKTM", data), s => - from language in s.GetField("SPRAS_ISO") - from description in s.GetField("MAKTX") - select new DescriptionData(language, description)) - - //extract some plant data of material master - from plantData in MapSegments(connection, FindSegments("E1MARCM", data), s => - from plant in s.GetField("WERKS") - from purchasingGroup in s.GetField("EKGRP") - select new PlantData(plant, purchasingGroup) - ) - select new MaterialMasterRecord( - material.MaterialNo, - material.ClientData, - descriptionData.ToArray(), - plantData.ToArray()); - } - - private static EitherAsync MapSegment(IConnection connection, - IDocDataRecord data, Func> mapFunc) - { - //to convert the segment we create a temporary structure of the segment definition type - //and "move" the segment data into it. - return connection.CreateStructure(MatmasTypes.Segment2Type[data.Segment]).Use(structure => - { - return from _ in structure.Bind(s => s.SetFromString(data.Data).ToAsync()) - from res in structure.Bind(s => mapFunc(s).ToAsync()) - select res; - }); - - } - - private static EitherAsync> MapSegments(IConnection connection, - Seq data, Func> mapFunc) - { - return data.Map(segment => MapSegment(connection, segment, mapFunc)) - .TraverseSerial(l => l); - } - - private static EitherAsync FindRequiredSegment( - string typeName, Seq records) - { - var segmentName = MatmasTypes.Type2Segment[typeName]; - return records.Find(x => x.Segment == segmentName) - .ToEither(RfcError.Error($"Segment {segmentName} not found")) - .ToAsync(); - } - - private static Seq FindSegments( - string typeName, Seq records) - { - var segmentName = MatmasTypes.Type2Segment[typeName]; - return records.Filter(x => x.Segment == segmentName); - } - - // for a known IDoc type you used fixed segment to type mapping - // a more generic way would be looking up segment names from RFM IDOCTYPE_READ_COMPLETE -} \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/SAPServerSettings.cs b/samples/net6.0/ExportMATMAS/SAPServerSettings.cs new file mode 100644 index 00000000..f780d34e --- /dev/null +++ b/samples/net6.0/ExportMATMAS/SAPServerSettings.cs @@ -0,0 +1,26 @@ +using Dbosoft.YaNco; +using Dbosoft.YaNco.TypeMapping; +using ExportMATMAS.MaterialMaster; + +namespace ExportMATMAS; + +public class SAPServerSettings : SAPRfcRuntimeSettings +{ + public TransactionManager TaManager { get; } + + public SAPServerSettings( + IFieldMapper fieldMapper, RfcRuntimeOptions options, + TransactionManager taManager) + : base(null, fieldMapper, options) + { + TaManager = taManager; + } + + public SAPServerSettings(ILogger? logger, + IFieldMapper fieldMapper, RfcRuntimeOptions options, + TransactionManager taManager) + : base(logger, fieldMapper, options) + { + TaManager = taManager; + } +} \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/TransactionState.cs b/samples/net6.0/ExportMATMAS/TransactionState.cs new file mode 100644 index 00000000..bc26c764 --- /dev/null +++ b/samples/net6.0/ExportMATMAS/TransactionState.cs @@ -0,0 +1,11 @@ +// ReSharper disable InconsistentNaming +namespace ExportMATMAS; + +public enum TransactionState +{ + Created, + Executed, + Committed, + RolledBack + +} \ No newline at end of file diff --git a/samples/net6.0/ExportMATMAS/TransactionStateRecord.cs b/samples/net6.0/ExportMATMAS/TransactionStateRecord.cs index c91938ec..b62e4b44 100644 --- a/samples/net6.0/ExportMATMAS/TransactionStateRecord.cs +++ b/samples/net6.0/ExportMATMAS/TransactionStateRecord.cs @@ -11,4 +11,6 @@ public TransactionStateRecord(string transactionId) TransactionId = transactionId; } -} \ No newline at end of file +} + +// ReSharper restore InconsistentNaming \ No newline at end of file diff --git a/samples/netcore2.1/SAPWebAPI/SAPWebAPI.csproj b/samples/netcore2.1/SAPWebAPI/SAPWebAPI.csproj index 5f631305..f26247a1 100644 --- a/samples/netcore2.1/SAPWebAPI/SAPWebAPI.csproj +++ b/samples/netcore2.1/SAPWebAPI/SAPWebAPI.csproj @@ -7,6 +7,7 @@ ..\..\.. false latest + Critical diff --git a/src/YaNco.Abstractions/AffOpt.cs b/src/YaNco.Abstractions/AffOpt.cs deleted file mode 100644 index f657ae75..00000000 --- a/src/YaNco.Abstractions/AffOpt.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Dbosoft.YaNco; - -internal static class AffOpt -{ - internal const MethodImplOptions mops = MethodImplOptions.AggressiveInlining; -} \ No newline at end of file diff --git a/src/YaNco.Abstractions/HasFieldMapper.cs b/src/YaNco.Abstractions/HasFieldMapper.cs deleted file mode 100644 index 28818758..00000000 --- a/src/YaNco.Abstractions/HasFieldMapper.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Dbosoft.YaNco.TypeMapping; -using JetBrains.Annotations; -using LanguageExt; - -namespace Dbosoft.YaNco; - -// ReSharper disable once InconsistentNaming -[PublicAPI] -public interface HasFieldMapper where RT : struct, HasFieldMapper -{ - Eff FieldMapperEff { get; } - -} \ No newline at end of file diff --git a/src/YaNco.Abstractions/HasSAPRfcClient.cs b/src/YaNco.Abstractions/HasSAPRfcClient.cs deleted file mode 100644 index 85a0f9d5..00000000 --- a/src/YaNco.Abstractions/HasSAPRfcClient.cs +++ /dev/null @@ -1,31 +0,0 @@ -using LanguageExt; -using LanguageExt.Effects.Traits; - -// ReSharper disable InconsistentNaming -#pragma warning disable IDE1006 - -namespace Dbosoft.YaNco; - - -public interface HasSAPRfcFunctions : HasCancel - where RT : struct, HasCancel -{ - Eff RfcFunctionsEff { get; } -} - -public interface HasSAPRfcConnection : HasCancel - where RT : struct, HasCancel -{ - Eff RfcConnectionEff { get; } -} - -public interface HasSAPRfcServer : HasCancel - where RT : struct, HasCancel -{ - Eff RfcServerEff { get; } -} - -public interface HasEnvRuntimeSettings -{ - SAPRfcRuntimeEnv Env { get; } -} \ No newline at end of file diff --git a/src/YaNco.Abstractions/HasSAPRfcData.cs b/src/YaNco.Abstractions/HasSAPRfcData.cs deleted file mode 100644 index 8ac4cbb6..00000000 --- a/src/YaNco.Abstractions/HasSAPRfcData.cs +++ /dev/null @@ -1,9 +0,0 @@ -using LanguageExt; - -namespace Dbosoft.YaNco; - -public interface HasSAPRfcData where RT : struct, HasSAPRfcData -{ - Eff RfcDataEff { get; } - -} diff --git a/src/YaNco.Abstractions/HasSAPRfcLogger.cs b/src/YaNco.Abstractions/HasSAPRfcLogger.cs deleted file mode 100644 index 85e57f75..00000000 --- a/src/YaNco.Abstractions/HasSAPRfcLogger.cs +++ /dev/null @@ -1,9 +0,0 @@ -using LanguageExt; - -namespace Dbosoft.YaNco; - -public interface HasSAPRfcLogger where RT : struct, HasSAPRfcLogger -{ - Eff> RfcLoggerEff { get; } - -} \ No newline at end of file diff --git a/src/YaNco.Abstractions/IConnection.cs b/src/YaNco.Abstractions/IConnection.cs index 2261cbd4..632611a7 100644 --- a/src/YaNco.Abstractions/IConnection.cs +++ b/src/YaNco.Abstractions/IConnection.cs @@ -119,5 +119,5 @@ public interface IConnection : IDisposable [Obsolete(Deprecations.RfcRuntime)] IRfcRuntime RfcRuntime { get; } - HasEnvRuntimeSettings ConnectionRuntime { get; } + IHasEnvRuntimeSettings ConnectionRuntime { get; } } \ No newline at end of file diff --git a/src/YaNco.Abstractions/IFunctionBuilder.cs b/src/YaNco.Abstractions/IFunctionBuilder.cs index f28434d1..1c8976a7 100644 --- a/src/YaNco.Abstractions/IFunctionBuilder.cs +++ b/src/YaNco.Abstractions/IFunctionBuilder.cs @@ -1,11 +1,12 @@ -using JetBrains.Annotations; +using Dbosoft.YaNco.Traits; +using JetBrains.Annotations; using LanguageExt; namespace Dbosoft.YaNco; [PublicAPI] public interface IFunctionBuilder - where RT : struct, HasSAPRfcFunctions + where RT : struct, HasSAPRfc { IFunctionBuilder AddParameter(RfcParameterDescription parameter); IFunctionBuilder AddChar(string name, RfcDirection direction, uint length, bool optional = true, string defaultValue = null); diff --git a/src/YaNco.Abstractions/IHasEnvRuntimeSettings.cs b/src/YaNco.Abstractions/IHasEnvRuntimeSettings.cs new file mode 100644 index 00000000..92a513f0 --- /dev/null +++ b/src/YaNco.Abstractions/IHasEnvRuntimeSettings.cs @@ -0,0 +1,15 @@ +namespace Dbosoft.YaNco; + +/// +/// This interface is used to provide access to the runtime settings of current runtime. +/// It exists for compatibility between OO with build-in runtime and functional runtime. +/// +/// +/// In OO runtime, the runtime settings are provided from defaults that can be overridden by the user. +/// In functional runtime, the runtime settings are provided by the runtime itself. +/// The interface allows to access the runtime settings in a uniform way. +/// +public interface IHasEnvRuntimeSettings +{ + SAPRfcRuntimeEnv Env { get; } +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/IRfcContext.cs b/src/YaNco.Abstractions/IRfcContext.cs index c58fec6b..de4181d9 100644 --- a/src/YaNco.Abstractions/IRfcContext.cs +++ b/src/YaNco.Abstractions/IRfcContext.cs @@ -14,7 +14,7 @@ public interface IRfcContext : IDisposable where RT : struct, HasCancel /// Creates a RFC function from function name. /// /// Name of the function as defined in SAP. - /// A with any rfc error as left state and function as right state. + /// A async effect with as success result. Aff CreateFunction(string name); @@ -22,59 +22,118 @@ public interface IRfcContext : IDisposable where RT : struct, HasCancel /// Calls the function specified in parameter function /// /// The function to be invoked. - /// A with any rfc error as left state and as right state. + /// A async effect with as success result. Aff InvokeFunction(IFunction function); /// /// Checks if connection to SAP backend could be established. /// - /// A with any rfc error as left state and as right state for chaining. + /// A async effect with as success result. Aff Ping(); /// /// Commits current SAP transaction in backend without waiting. /// - /// A with any rfc error as left state and as right state. + /// A async effect with as success result. Aff Commit(); /// /// Commits current SAP transaction in backend with waiting for posting to be completed. /// - /// A with any rfc error as left state and as right state. + /// A async effect with as success result. Aff CommitAndWait(); /// /// Rollback of current SAP transaction in backend. /// - /// A with any rfc error as left state and as right state. + /// A async effect with as success result. Aff Rollback(); /// /// Explicit request to open a connection to the SAP backend. /// /// - /// Normally it is not necessary to access the connection directly. However if you would like to - /// access you can use this method to first open a connection and then to interact directly with the - /// SAP backend via property of the opened connection. + /// Normally it is not necessary to access the connection directly. + /// However you can access the connection directly to perform low level operations on the connection. /// - /// A with any rfc error as left state and as right state. + /// A async effect with as success result. Aff GetConnection(); + // ReSharper disable InconsistentNaming + + /// + /// Calls a SAP RFM as async effect with input and output + /// + /// + /// The input parameter of this method is a function that maps from a + /// to any kind of type. The input type itself is not used any more + /// after calling the input mapping. + /// The output parameter of this method is also a function that maps from a + /// to any kind of type. The output type is returned after processing the ABAP function. + /// + /// You should use the methods defined on within the mapping functions to map from .NET + /// types to SAP function fields and back from SAP function fields to .NET. + /// + /// + /// + /// name of the function as defined in SAP backend + /// Input function lifted in either monad. + /// Output function lifted in either monad. + /// Result of output mapping function as a async effect Aff CallFunction( string functionName, Func, Either> Input, Func, Either> Output); + /// + /// Calls a SAP RFM as async effect with output and no input. + /// + /// + /// The output parameter of this method is also a function that maps from a + /// to any kind of type. The output type is returned after processing the ABAP function. + /// + /// You should use the methods defined on within the mapping functions to map from .NET + /// types to SAP function fields and back from SAP function fields to .NET. + /// + /// + /// name of the function as defined in SAP backend + /// Output function lifted in either monad. + /// Result of output mapping function as a async effect Aff CallFunction( string functionName, Func, Either> Output); + /// + /// Calls a SAP RFM as async effect with no output and no input. + /// + /// + /// + /// name of the function as defined in SAP backend + /// A async effect + Aff InvokeFunction( string functionName); + /// + /// Calls a SAP RFM as async effect with input and no output. + /// + /// + /// The input parameter of this method is a function that maps from a + /// to any kind of type. The input type itself is not used any more + /// after calling the input mapping. + /// You should use the methods defined on within the mapping functions to map from .NET + /// types to SAP function fields and back from SAP function fields to .NET. + /// + /// + /// name of the function as defined in SAP backend + /// Input function lifted in either monad. + /// A async effect Aff InvokeFunction( string functionName, Func, Either> Input); + + // ReSharper restore InconsistentNaming + } [PublicAPI] diff --git a/src/YaNco.Abstractions/IRfcRuntime.cs b/src/YaNco.Abstractions/IRfcRuntime.cs index 9bc52c1a..b429685f 100644 --- a/src/YaNco.Abstractions/IRfcRuntime.cs +++ b/src/YaNco.Abstractions/IRfcRuntime.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using Dbosoft.YaNco.Traits; using Dbosoft.YaNco.TypeMapping; using JetBrains.Annotations; using LanguageExt; @@ -7,135 +7,6 @@ namespace Dbosoft.YaNco; -public interface SAPRfcDataIO: SAPRfcTypeIO, SAPRfcStructureIO, SAPRfcTableIO, SAPRfcFieldIO -{ -} - -public interface SAPRfcTypeIO -{ - Either GetTypeDescription(IDataContainerHandle dataContainer); - Either GetTypeDescription(IConnectionHandle connectionHandle, string typeName); - Either GetTypeFieldCount(ITypeDescriptionHandle descriptionHandle); - - Either GetTypeFieldDescription(ITypeDescriptionHandle descriptionHandle, - int index); - - Either GetTypeFieldDescription(ITypeDescriptionHandle descriptionHandle, - string name); - - -} - -public interface SAPRfcStructureIO -{ - Either GetStructure(IDataContainerHandle dataContainer, string name); - Either CreateStructure(ITypeDescriptionHandle typeDescriptionHandle); - Either SetStructure(IStructureHandle structureHandle, string content); - -} - -public interface SAPRfcTableIO -{ - RfcRuntimeOptions Options { get; } - - Either GetTable(IDataContainerHandle dataContainer, string name); - Either CloneTable(ITableHandle tableHandle); - - Either GetTableRowCount(ITableHandle tableHandle); - Either GetCurrentTableRow(ITableHandle tableHandle); - Either AppendTableRow(ITableHandle tableHandle); - Either MoveToNextTableRow(ITableHandle tableHandle); - Either MoveToFirstTableRow(ITableHandle tableHandle); - -} - -public interface SAPRfcFunctionIO -{ - Either CreateFunction(IFunctionDescriptionHandle descriptionHandle); - - Either GetFunctionParameterCount(IFunctionDescriptionHandle descriptionHandle); - - Either GetFunctionParameterDescription( - IFunctionDescriptionHandle descriptionHandle, int index); - - Either GetFunctionParameterDescription( - IFunctionDescriptionHandle descriptionHandle, string name); - - Either AddFunctionHandler(string sysid, - IFunction function, Func> handler); - - Either AddFunctionHandler(string sysid, - IFunctionDescriptionHandle descriptionHandle, Func> handler); - - Either Invoke(IConnectionHandle connectionHandle, IFunctionHandle functionHandle); - Either CreateFunctionDescription(string functionName); - Either AddFunctionParameter(IFunctionDescriptionHandle descriptionHandle, RfcParameterDescription parameterDescription); - - Either GetFunctionDescription(IConnectionHandle connectionHandle, - string functionName); - - Either GetFunctionDescription(IFunctionHandle functionHandle); - Either GetFunctionName(IFunctionDescriptionHandle descriptionHandle); - -} - -public interface SAPRfcConnectionIO -{ - Either OpenConnection(IDictionary connectionParams); - Either CancelConnection(IConnectionHandle connectionHandle); - Either IsConnectionHandleValid(IConnectionHandle connectionHandle); - Either GetConnectionAttributes(IConnectionHandle connectionHandle); - -} - -public interface SAPRfcServerIO -{ - Either CreateServer(IDictionary connectionParams); - Either LaunchServer(IRfcServerHandle rfcServerHandle); - Either ShutdownServer(IRfcServerHandle rfcServerHandle, int timeout); - Either GetServerCallContext(IRfcHandle rfcHandle); - - Either AddTransactionHandlers(string sysid, - Func onCheck, - Func onCommit, - Func onRollback, - Func onConfirm); - - -} - -public interface SAPRfcFieldIO -{ - Either SetString(IDataContainerHandle containerHandle, string name, - string value); - - Either GetString(IDataContainerHandle containerHandle, string name); - - Either SetDateString(IDataContainerHandle containerHandle, string name, - string value); - - Either GetDateString(IDataContainerHandle containerHandle, string name); - - Either SetTimeString(IDataContainerHandle containerHandle, string name, - string value); - - Either GetTimeString(IDataContainerHandle containerHandle, string name); - Either SetInt(IDataContainerHandle containerHandle, string name, int value); - Either GetInt(IDataContainerHandle containerHandle, string name); - Either SetLong(IDataContainerHandle containerHandle, string name, long value); - Either GetLong(IDataContainerHandle containerHandle, string name); - Either SetBytes(IDataContainerHandle containerHandle, string name, byte[] buffer, long bufferLength); - Either GetBytes(IDataContainerHandle containerHandle, string name); - - Either SetFieldValue(IDataContainerHandle handle, T value, Func> func); - Either GetFieldValue(IDataContainerHandle handle, Func> func); - - Either GetValue(AbapValue abapValue); - Either SetValue(T value, RfcFieldInfo fieldInfo); - -} - - [Obsolete(Deprecations.RfcRuntime)] [PublicAPI] public interface IRfcRuntime : diff --git a/src/YaNco.Abstractions/ITransactionalRfcHandler.cs b/src/YaNco.Abstractions/ITransactionalRfcHandler.cs index a6d74fb1..ccc66552 100644 --- a/src/YaNco.Abstractions/ITransactionalRfcHandler.cs +++ b/src/YaNco.Abstractions/ITransactionalRfcHandler.cs @@ -1,11 +1,13 @@ -namespace Dbosoft.YaNco; +using LanguageExt; + +namespace Dbosoft.YaNco; // ReSharper disable once TypeParameterCanBeVariant public interface ITransactionalRfcHandler where RT : struct { - RfcRc OnCheck(RT runtime, IRfcHandle rfcHandle, string transactionId); - RfcRc OnCommit(RT runtime, IRfcHandle rfcHandle, string transactionId); - RfcRc OnRollback(RT runtime, IRfcHandle rfcHandle, string transactionId); - RfcRc OnConfirm(RT runtime, IRfcHandle rfcHandle, string transactionId); + Eff OnCheck(IRfcHandle rfcHandle, string transactionId); + Eff OnCommit(IRfcHandle rfcHandle, string transactionId); + Eff OnRollback(IRfcHandle rfcHandle, string transactionId); + Eff OnConfirm(IRfcHandle rfcHandle, string transactionId); } \ No newline at end of file diff --git a/src/YaNco.Abstractions/SAPRfcRuntimeEnv.cs b/src/YaNco.Abstractions/SAPRfcRuntimeEnv.cs index fb1d66bc..5f7df47e 100644 --- a/src/YaNco.Abstractions/SAPRfcRuntimeEnv.cs +++ b/src/YaNco.Abstractions/SAPRfcRuntimeEnv.cs @@ -21,4 +21,10 @@ public SAPRfcRuntimeEnv(CancellationTokenSource source, TSettings settings) { } + /// + /// converts the settings to base runtime settings + /// + public SAPRfcRuntimeEnv ToRuntimeSettings() => + new(Source, Settings); + } \ No newline at end of file diff --git a/src/YaNco.Abstractions/SAPRfcRuntimeSettings.cs b/src/YaNco.Abstractions/SAPRfcRuntimeSettings.cs index a0c17174..94c1d16e 100644 --- a/src/YaNco.Abstractions/SAPRfcRuntimeSettings.cs +++ b/src/YaNco.Abstractions/SAPRfcRuntimeSettings.cs @@ -1,18 +1,52 @@ -using Dbosoft.YaNco.TypeMapping; +using Dbosoft.YaNco.Traits; +using Dbosoft.YaNco.TypeMapping; namespace Dbosoft.YaNco; +/// +/// This class holds the runtime settings for the SAP RFC runtime +/// +/// +/// If you would like to inject your own implementations for logger +/// and field mapper via dependency injection, you can register this class in your DI container. +/// public class SAPRfcRuntimeSettings { public readonly IFieldMapper FieldMapper; public readonly ILogger Logger; - public readonly RfcRuntimeOptions TableOptions; + public readonly RfcRuntimeOptions Options; - public SAPRfcRuntimeSettings(ILogger logger, IFieldMapper fieldMapper, RfcRuntimeOptions tableOptions) + /// + /// Creates a new instance of the runtime settings with the given field mapper + /// + /// the field mapper instance + public static SAPRfcRuntimeSettings New(IFieldMapper fieldMapper) + => new(null, fieldMapper, new RfcRuntimeOptions()); + + /// + /// Creates a new instance of the runtime settings with the given logger and field mapper + /// + /// The logger instance + /// the field mapper instance + public static SAPRfcRuntimeSettings New(ILogger logger, IFieldMapper fieldMapper) + => new(logger, fieldMapper, new RfcRuntimeOptions()); + + /// + /// Default constructor + /// + /// + /// You can pass null for logger if you don't want to use logging. + /// The default field mapper can be created by calling or + /// . + /// + /// + /// + /// + public SAPRfcRuntimeSettings(ILogger logger, IFieldMapper fieldMapper, RfcRuntimeOptions options) { FieldMapper = fieldMapper; Logger = logger; - TableOptions = tableOptions; + Options = options; } public SAPRfcDataIO RfcDataIO { get; set; } diff --git a/src/YaNco.Abstractions/Traits/HasSAPRfc.cs b/src/YaNco.Abstractions/Traits/HasSAPRfc.cs new file mode 100644 index 00000000..70c215e4 --- /dev/null +++ b/src/YaNco.Abstractions/Traits/HasSAPRfc.cs @@ -0,0 +1,18 @@ +using Dbosoft.YaNco.TypeMapping; +using JetBrains.Annotations; +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +[PublicAPI] +public interface HasSAPRfc : IHasEnvRuntimeSettings + where RT : struct +{ + Eff RfcConnectionEff { get; } + Eff RfcDataEff { get; } + Eff RfcFunctionsEff { get; } + Eff> RfcLoggerEff { get; } + Eff FieldMapperEff { get; } + +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/HasSAPRfcServer.cs b/src/YaNco.Abstractions/Traits/HasSAPRfcServer.cs new file mode 100644 index 00000000..49a7c088 --- /dev/null +++ b/src/YaNco.Abstractions/Traits/HasSAPRfcServer.cs @@ -0,0 +1,10 @@ +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +public interface HasSAPRfcServer + where RT : struct, HasSAPRfc +{ + Eff RfcServerEff { get; } +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/SAPRfcConnectionIO.cs b/src/YaNco.Abstractions/Traits/SAPRfcConnectionIO.cs new file mode 100644 index 00000000..2e35d8b6 --- /dev/null +++ b/src/YaNco.Abstractions/Traits/SAPRfcConnectionIO.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +public interface SAPRfcConnectionIO +{ + Either OpenConnection(IDictionary connectionParams); + Either CancelConnection(IConnectionHandle connectionHandle); + Either IsConnectionHandleValid(IConnectionHandle connectionHandle); + Either GetConnectionAttributes(IConnectionHandle connectionHandle); + +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/SAPRfcDataIO.cs b/src/YaNco.Abstractions/Traits/SAPRfcDataIO.cs new file mode 100644 index 00000000..3df5f8e3 --- /dev/null +++ b/src/YaNco.Abstractions/Traits/SAPRfcDataIO.cs @@ -0,0 +1,6 @@ +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +public interface SAPRfcDataIO : SAPRfcTypeIO, SAPRfcStructureIO, SAPRfcTableIO, SAPRfcFieldIO +{ +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/SAPRfcFieldIO.cs b/src/YaNco.Abstractions/Traits/SAPRfcFieldIO.cs new file mode 100644 index 00000000..c8efff1e --- /dev/null +++ b/src/YaNco.Abstractions/Traits/SAPRfcFieldIO.cs @@ -0,0 +1,37 @@ +using System; +using Dbosoft.YaNco.TypeMapping; +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +public interface SAPRfcFieldIO +{ + Either SetString(IDataContainerHandle containerHandle, string name, + string value); + + Either GetString(IDataContainerHandle containerHandle, string name); + + Either SetDateString(IDataContainerHandle containerHandle, string name, + string value); + + Either GetDateString(IDataContainerHandle containerHandle, string name); + + Either SetTimeString(IDataContainerHandle containerHandle, string name, + string value); + + Either GetTimeString(IDataContainerHandle containerHandle, string name); + Either SetInt(IDataContainerHandle containerHandle, string name, int value); + Either GetInt(IDataContainerHandle containerHandle, string name); + Either SetLong(IDataContainerHandle containerHandle, string name, long value); + Either GetLong(IDataContainerHandle containerHandle, string name); + Either SetBytes(IDataContainerHandle containerHandle, string name, byte[] buffer, long bufferLength); + Either GetBytes(IDataContainerHandle containerHandle, string name); + + Either SetFieldValue(IDataContainerHandle handle, T value, Func> func); + Either GetFieldValue(IDataContainerHandle handle, Func> func); + + Either GetValue(AbapValue abapValue); + Either SetValue(T value, RfcFieldInfo fieldInfo); + +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/SAPRfcFunctionIO.cs b/src/YaNco.Abstractions/Traits/SAPRfcFunctionIO.cs new file mode 100644 index 00000000..3fc1b0ab --- /dev/null +++ b/src/YaNco.Abstractions/Traits/SAPRfcFunctionIO.cs @@ -0,0 +1,35 @@ +using System; +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +public interface SAPRfcFunctionIO +{ + Either CreateFunction(IFunctionDescriptionHandle descriptionHandle); + + Either GetFunctionParameterCount(IFunctionDescriptionHandle descriptionHandle); + + Either GetFunctionParameterDescription( + IFunctionDescriptionHandle descriptionHandle, int index); + + Either GetFunctionParameterDescription( + IFunctionDescriptionHandle descriptionHandle, string name); + + Either AddFunctionHandler(string sysid, + IFunction function, Func> handler); + + Either AddFunctionHandler(string sysid, + IFunctionDescriptionHandle descriptionHandle, Func> handler); + + Either Invoke(IConnectionHandle connectionHandle, IFunctionHandle functionHandle); + Either CreateFunctionDescription(string functionName); + Either AddFunctionParameter(IFunctionDescriptionHandle descriptionHandle, RfcParameterDescription parameterDescription); + + Either GetFunctionDescription(IConnectionHandle connectionHandle, + string functionName); + + Either GetFunctionDescription(IFunctionHandle functionHandle); + Either GetFunctionName(IFunctionDescriptionHandle descriptionHandle); + +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/SAPRfcServerIO.cs b/src/YaNco.Abstractions/Traits/SAPRfcServerIO.cs new file mode 100644 index 00000000..c510998a --- /dev/null +++ b/src/YaNco.Abstractions/Traits/SAPRfcServerIO.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +public interface SAPRfcServerIO +{ + Either CreateServer(IDictionary connectionParams); + Either LaunchServer(IRfcServerHandle rfcServerHandle); + Either ShutdownServer(IRfcServerHandle rfcServerHandle, int timeout); + Either GetServerCallContext(IRfcHandle rfcHandle); + + Either AddTransactionHandlers(string sysid, + Func onCheck, + Func onCommit, + Func onRollback, + Func onConfirm); + + +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/SAPRfcStructureIO.cs b/src/YaNco.Abstractions/Traits/SAPRfcStructureIO.cs new file mode 100644 index 00000000..b22233da --- /dev/null +++ b/src/YaNco.Abstractions/Traits/SAPRfcStructureIO.cs @@ -0,0 +1,12 @@ +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +public interface SAPRfcStructureIO +{ + Either GetStructure(IDataContainerHandle dataContainer, string name); + Either CreateStructure(ITypeDescriptionHandle typeDescriptionHandle); + Either SetStructure(IStructureHandle structureHandle, string content); + +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/SAPRfcTableIO.cs b/src/YaNco.Abstractions/Traits/SAPRfcTableIO.cs new file mode 100644 index 00000000..4df267df --- /dev/null +++ b/src/YaNco.Abstractions/Traits/SAPRfcTableIO.cs @@ -0,0 +1,19 @@ +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +public interface SAPRfcTableIO +{ + RfcRuntimeOptions Options { get; } + + Either GetTable(IDataContainerHandle dataContainer, string name); + Either CloneTable(ITableHandle tableHandle); + + Either GetTableRowCount(ITableHandle tableHandle); + Either GetCurrentTableRow(ITableHandle tableHandle); + Either AppendTableRow(ITableHandle tableHandle); + Either MoveToNextTableRow(ITableHandle tableHandle); + Either MoveToFirstTableRow(ITableHandle tableHandle); + +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/Traits/SAPRfcTypeIO.cs b/src/YaNco.Abstractions/Traits/SAPRfcTypeIO.cs new file mode 100644 index 00000000..5856da52 --- /dev/null +++ b/src/YaNco.Abstractions/Traits/SAPRfcTypeIO.cs @@ -0,0 +1,19 @@ +using LanguageExt; + +namespace Dbosoft.YaNco.Traits; + +// ReSharper disable once InconsistentNaming +public interface SAPRfcTypeIO +{ + Either GetTypeDescription(IDataContainerHandle dataContainer); + Either GetTypeDescription(IConnectionHandle connectionHandle, string typeName); + Either GetTypeFieldCount(ITypeDescriptionHandle descriptionHandle); + + Either GetTypeFieldDescription(ITypeDescriptionHandle descriptionHandle, + int index); + + Either GetTypeFieldDescription(ITypeDescriptionHandle descriptionHandle, + string name); + + +} \ No newline at end of file diff --git a/src/YaNco.Abstractions/TypeMapping/FieldMappingContext.cs b/src/YaNco.Abstractions/TypeMapping/FieldMappingContext.cs index e09518c3..e5285bcc 100644 --- a/src/YaNco.Abstractions/TypeMapping/FieldMappingContext.cs +++ b/src/YaNco.Abstractions/TypeMapping/FieldMappingContext.cs @@ -1,4 +1,6 @@ -namespace Dbosoft.YaNco.TypeMapping; +using Dbosoft.YaNco.Traits; + +namespace Dbosoft.YaNco.TypeMapping; public class FieldMappingContext { diff --git a/src/YaNco.Abstractions/TypeMapping/IFieldMapper.cs b/src/YaNco.Abstractions/TypeMapping/IFieldMapper.cs index cfd80b4c..743072bd 100644 --- a/src/YaNco.Abstractions/TypeMapping/IFieldMapper.cs +++ b/src/YaNco.Abstractions/TypeMapping/IFieldMapper.cs @@ -1,13 +1,45 @@ -using LanguageExt; +using Dbosoft.YaNco.Traits; +using LanguageExt; namespace Dbosoft.YaNco.TypeMapping; +/// +/// The field mapper is responsible for mapping between .NET types and SAP RFC types +/// public interface IFieldMapper { + /// + /// Sets a field value in a RFC field. For internal use by + /// + /// + /// + /// Mapping context build by the IO. + /// Either SetField(T value, FieldMappingContext context); + + /// + /// Gets a field value from a RFC field. For internal use by + /// + /// + /// + /// Either GetField(FieldMappingContext context); + /// + /// Maps a SAP RFC value to a .NET type + /// + /// + /// + /// Either FromAbapValue(AbapValue abapValue); + + /// + /// Maps a .NET value to a SAP RFC value + /// + /// + /// + /// + /// Either ToAbapValue(T value, RfcFieldInfo fieldInfo); } \ No newline at end of file diff --git a/src/YaNco.Core/CalledFunction.cs b/src/YaNco.Core/CalledFunction.cs index 8838fa38..4e8c38d9 100644 --- a/src/YaNco.Core/CalledFunction.cs +++ b/src/YaNco.Core/CalledFunction.cs @@ -1,12 +1,14 @@ using System; +using Dbosoft.YaNco.Traits; using JetBrains.Annotations; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; [PublicAPI] public readonly struct CalledFunction where RT : struct, - HasSAPRfcFunctions, HasSAPRfcConnection, HasSAPRfcLogger, HasSAPRfcData + HasSAPRfc, HasCancel { public readonly IFunction Function; private readonly Func> _rfcContextFunc; @@ -24,7 +26,7 @@ internal CalledFunction(IRfcHandle rfcHandle, IFunction function, /// /// Type of data extracted from function. Could be any type. /// Function to map from RFC function to the desired input type - /// wrapped in a + /// wrapped in a public Either> Input(Func, Either> inputFunc) { var function = Function; diff --git a/src/YaNco.Core/Connection.cs b/src/YaNco.Core/Connection.cs index 5cbc21a3..f5b3b01d 100644 --- a/src/YaNco.Core/Connection.cs +++ b/src/YaNco.Core/Connection.cs @@ -4,7 +4,9 @@ using System.Threading.Tasks; using Dbosoft.Functional; using Dbosoft.YaNco.Live; +using Dbosoft.YaNco.Traits; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; @@ -12,7 +14,7 @@ namespace Dbosoft.YaNco; /// Default implementation of /// public class Connection : IConnection - where RT : struct,HasSAPRfcLogger, HasSAPRfcData, HasSAPRfcFunctions, HasSAPRfcConnection, HasEnvRuntimeSettings + where RT : struct, HasSAPRfc, HasCancel { private readonly RT _runtime; private readonly IConnectionHandle _connectionHandle; @@ -25,7 +27,7 @@ public class Connection : IConnection public IRfcRuntime RfcRuntime => new RfcRuntime(SAPRfcRuntime.New( _runtime.Env.Source, _runtime.Env.Settings)); - public HasEnvRuntimeSettings ConnectionRuntime => _runtime; + public IHasEnvRuntimeSettings ConnectionRuntime => _runtime; public Connection( RT runtime, @@ -120,6 +122,7 @@ from result in Unit.Default.Apply( _ => var res = effect.ToEither(runtime); + if (res.IsBottom) return (handle, RfcError.New($"connection message {msg.GetType()} returned a bottom state. " + $"This typical occurs in Unit testing if not all required methods have been setup. Message details: {msg}")); diff --git a/src/YaNco.Core/ConnectionBuilder.cs b/src/YaNco.Core/ConnectionBuilder.cs index fa0994cd..3b5e5f7c 100644 --- a/src/YaNco.Core/ConnectionBuilder.cs +++ b/src/YaNco.Core/ConnectionBuilder.cs @@ -2,16 +2,35 @@ using System.Collections.Generic; using System.Threading; using Dbosoft.YaNco.Live; +using Dbosoft.YaNco.Traits; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; +/// +/// This class is used to build client connections to a SAP ABAP backend. +/// +/// The non-generic version of this class uses the build-in runtime . +/// To use a custom runtime, use instead. +/// +/// public class ConnectionBuilder : ConnectionBuilderBase { public ConnectionBuilder(IDictionary connectionParam) : base(connectionParam) { } + /// + /// This methods builds the factory function + /// with a build-in runtime of type + /// + /// + /// The runtime is created with the configuration actions registered with + /// If you want to use a custom runtime, use instead. + /// Multiple calls of this method will return the same factory function. + /// + /// with the public new Func> Build() { var baseEffect = base.Build(); @@ -23,12 +42,21 @@ public ConnectionBuilder(IDictionary connectionParam) : base(con }; } + /// + /// This methods builds the async effect to create the + /// + /// + /// The runtime is created with the configuration actions registered with + /// If you want to use a custom runtime, use instead. + /// Multiple calls of this method will return the same effect. + /// + /// with the public Aff BuildIO() => base.Build(); /// /// Registers a action to configure the /// - /// action with + /// action with /// current instance for chaining. /// /// Multiple calls of this method will override the previous configuration action. @@ -42,8 +70,9 @@ public ConnectionBuilder ConfigureRuntime(Action /// This class is used to build client connections to a SAP ABAP backend. /// +/// The runtime to be used for the connection public class ConnectionBuilder : ConnectionBuilderBase, RT> - where RT : struct, HasSAPRfcFunctions, HasSAPRfcServer, HasSAPRfcConnection, HasSAPRfcLogger, HasSAPRfcData, HasEnvRuntimeSettings + where RT : struct, HasSAPRfc, HasCancel { public ConnectionBuilder(IDictionary connectionParam) : base(connectionParam) diff --git a/src/YaNco.Core/ConnectionBuilderBase.cs b/src/YaNco.Core/ConnectionBuilderBase.cs index ca8e2929..cb24319a 100644 --- a/src/YaNco.Core/ConnectionBuilderBase.cs +++ b/src/YaNco.Core/ConnectionBuilderBase.cs @@ -1,22 +1,20 @@ using System; using System.Collections.Generic; using System.Linq; +using Dbosoft.YaNco.Traits; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; /// -/// This class is used to build client connections to a SAP ABAP backend. +/// This class is used to build client connections to a SAP ABAP backend. /// +/// The builder type for chaining +/// Runtime type public class ConnectionBuilderBase : RfcBuilderBase where TBuilder: ConnectionBuilderBase - where RT : struct, - HasSAPRfcFunctions, - HasSAPRfcServer, - HasSAPRfcConnection, - HasSAPRfcLogger, - HasSAPRfcData, - HasEnvRuntimeSettings + where RT : struct, HasSAPRfc, HasCancel { private readonly IDictionary _connectionParam; private IFunctionRegistration _functionRegistration = FunctionRegistration.Instance; @@ -39,8 +37,8 @@ public ConnectionBuilderBase(IDictionary connectionParam) /// Use a alternative factory method to create connection. /// /// factory method - /// current instance for chaining.The default implementation call . + /// current instance of for chaining. + /// The default implementation call . /// public TBuilder UseFactory( Func, RT, Eff> factory) @@ -71,7 +69,7 @@ public TBuilder WithFunctionRegistration(IFunctionRegistration functionRegistrat /// /// The metadata of the function is retrieved from the backend. Therefore the function /// must exists on the SAP backend system. - /// To register a generic function use the signature that builds from a . + /// To register a generic function use the signature that builds from a . /// Function handlers are registered process wide (in the SAP NW RFC Library) and mapped to backend system id. /// Multiple registrations of same function and same backend id will therefore have no effect. /// @@ -84,12 +82,11 @@ public TBuilder WithFunctionHandler(string functionName, /// - /// This method Builds the connection function from the settings. + /// This method Builds the connection IO effect from the settings. /// - /// . + /// with /// - /// The connection builder first creates RfcRuntime and calls any registered runtime configure action. - /// The result is a function that first opens a connection and afterwards registers function handlers. + /// The result is a effect that first opens a connection and afterwards registers function handlers. /// public Aff Build() { diff --git a/src/YaNco.Core/ConnectionPlaceholder.cs b/src/YaNco.Core/ConnectionPlaceholder.cs index 8b8d3b9a..1d2b13df 100644 --- a/src/YaNco.Core/ConnectionPlaceholder.cs +++ b/src/YaNco.Core/ConnectionPlaceholder.cs @@ -84,6 +84,6 @@ public EitherAsync GetAttributes() [Obsolete(Deprecations.RfcRuntime)] public IRfcRuntime RfcRuntime { get; } = new RfcRuntime(SAPRfcRuntime.Default); - public HasEnvRuntimeSettings ConnectionRuntime { get; } = SAPRfcRuntime.Default; + public IHasEnvRuntimeSettings ConnectionRuntime { get; } = SAPRfcRuntime.Default; } \ No newline at end of file diff --git a/src/YaNco.Core/DataContainer.cs b/src/YaNco.Core/DataContainer.cs index abf80499..f1fe9627 100644 --- a/src/YaNco.Core/DataContainer.cs +++ b/src/YaNco.Core/DataContainer.cs @@ -1,4 +1,5 @@ using System; +using Dbosoft.YaNco.Traits; using LanguageExt; namespace Dbosoft.YaNco; diff --git a/src/YaNco.Core/Function.cs b/src/YaNco.Core/Function.cs index 8aa10423..99dc6a39 100644 --- a/src/YaNco.Core/Function.cs +++ b/src/YaNco.Core/Function.cs @@ -1,4 +1,5 @@ -using LanguageExt; +using Dbosoft.YaNco.Traits; +using LanguageExt; namespace Dbosoft.YaNco; diff --git a/src/YaNco.Core/FunctionBuilder.cs b/src/YaNco.Core/FunctionBuilder.cs index c1c063ac..b2480e2a 100644 --- a/src/YaNco.Core/FunctionBuilder.cs +++ b/src/YaNco.Core/FunctionBuilder.cs @@ -1,12 +1,13 @@ using System; using System.Collections.Generic; using Dbosoft.YaNco.Internal; +using Dbosoft.YaNco.Traits; using LanguageExt; namespace Dbosoft.YaNco; public class FunctionBuilder : IFunctionBuilder - where RT : struct, HasSAPRfcFunctions + where RT : struct, HasSAPRfc { private readonly string _functionName; diff --git a/src/YaNco.Core/FunctionRegistration.cs b/src/YaNco.Core/FunctionRegistration.cs index 54801fbc..2b3b8fe7 100644 --- a/src/YaNco.Core/FunctionRegistration.cs +++ b/src/YaNco.Core/FunctionRegistration.cs @@ -57,9 +57,9 @@ public void Remove(string sysId, string functionName) public void Dispose() { - foreach (var registration in _registrations) + foreach (var (_, value) in _registrations) { - registration.Value.Holder.Dispose(); + value.Holder.Dispose(); } _registrations = HashMap.Empty; } diff --git a/src/YaNco.Core/FunctionalFunctionsExtensions.cs b/src/YaNco.Core/FunctionalFunctionsExtensions.cs index 58236c1c..799aa8e0 100644 --- a/src/YaNco.Core/FunctionalFunctionsExtensions.cs +++ b/src/YaNco.Core/FunctionalFunctionsExtensions.cs @@ -171,10 +171,10 @@ private static Either ErrorOrResult(TResult result, /// CallFunction with input and output and lifted input and output functions. /// /// - /// The input parameter of this method is a function that maps from a + /// The input parameter of this method is a function that maps from a /// to any kind of type. The input type itself is not used any more /// after calling the input mapping. - /// The output parameter of this method is also a function that maps from a + /// The output parameter of this method is also a function that maps from a /// to any kind of type. The output type is returned after processing the ABAP function. /// /// You should use the methods defined on within the mapping functions to map from .NET @@ -210,7 +210,7 @@ from output in Output(Prelude.Right(func)).ToAsync() /// CallFunction with RfcError lifted output. /// /// - /// The output parameter of this method is a function that maps from a + /// The output parameter of this method is a function that maps from a /// to any kind of type. The output type is returned after processing the ABAP function. /// /// You should use the methods defined on within the output mapping function to map @@ -258,7 +258,7 @@ public static EitherAsync CallFunctionOneWay( /// CallFunction with RfcInfo lifted input and no output. /// /// - /// The input parameter of this method is a function that maps from a + /// The input parameter of this method is a function that maps from a /// to any kind of type. The input type itself is not used any more /// after calling the input mapping. /// You should use the methods defined on within the input mapping functions to map from .NET diff --git a/src/YaNco.Core/FunctionalServerExtensions.cs b/src/YaNco.Core/FunctionalServerExtensions.cs index 5e67900b..5d1135e2 100644 --- a/src/YaNco.Core/FunctionalServerExtensions.cs +++ b/src/YaNco.Core/FunctionalServerExtensions.cs @@ -16,6 +16,7 @@ public static class FunctionalServerExtensions /// /// Type of data returned from processing. Could be any type. /// Type of data returned from rfc input. Could be any type. + /// the runtime for the call /// input from previous chain step. /// Function to map from to "/> /// @@ -31,6 +32,7 @@ public static Eff> Process( /// /// Type of data returned from processing. Could be any type. /// Type of data returned from rfc input. Could be any type. + /// the runtime /// Function to map from to "/> /// input from previous chain step. /// @@ -47,9 +49,10 @@ public static Aff> Process( /// /// Type of data returned from processing. Could be any type. /// Type of data returned from rfc input. Could be any type. + /// the runtime /// Function to map from to "/> /// input from previous chain step. - /// + /// in a public static Aff> ProcessAsync( this Either> input, Func> processFunc) where RT : struct, HasCancel @@ -62,9 +65,10 @@ public static Aff> ProcessAsync /// Type of data returned from rfc input. Could be any type. + /// the runtime /// input from previous chain step. /// Action to process . - /// "/> + /// "/> in a public static Eff> Process( this Either> input, Action processAction) where RT : struct, HasCancel @@ -77,14 +81,23 @@ public static Eff> Process( }).ToEff(l=>l); } + /// + /// Data processing for a called function. Use this method to do any work you would like to do when the function is called. + /// + /// Type of data returned from rfc input. Could be any type. + /// the runtime + /// input from previous chain step. + /// Function to process that returns a . + /// "/> in a + public static Aff> ProcessAsync( this Either> input, - Func processAction) where RT : struct, HasCancel + Func processFunc) where RT : struct, HasCancel { return input.ToEff(l => l).Bind(i => Prelude.Aff(async () => { var (function, input1) = i; - await processAction(input1).ConfigureAwait(false); + await processFunc(input1).ConfigureAwait(false); return new FunctionProcessed(Unit.Default, function); })); } @@ -93,6 +106,7 @@ public static Aff> ProcessAsync( /// Use this method to send a response to sap backend after processing the function call. /// /// type of Output of processing chain step. + /// runtime used /// previous chain step /// Function to map from to rfc function. /// @@ -106,6 +120,7 @@ public static Eff Reply(this Eff /// type of Output of processing chain step. + /// runtime used /// previous chain step /// Function to map from to rfc function. /// wrapped in a @@ -119,6 +134,7 @@ public static Aff Reply(this Aff /// type of Output of processing chain step. + /// runtime used /// previous chain step /// wrapped in a public static Eff NoReply(this Eff> self) where RT : struct @@ -130,6 +146,7 @@ public static Eff NoReply(this Eff /// type of Output of processing chain step. + /// runtime used /// previous chain step /// wrapped in a public static Aff NoReply(this Aff> self) where RT : struct, HasCancel @@ -153,7 +170,7 @@ public static EitherAsync> Start(this EitherAsync /// RFC Server, wrapping in a /// Started RFC Server if Either is not left. Otherwise a exception will be thrown. - /// Use this method to integrate the with services that expect exceptions on failure. + /// Use this method to integrate the with services that expect exceptions on failure. /// public static async Task> StartOrException(this EitherAsync> eitherServer) where RT : struct, HasCancel diff --git a/src/YaNco.Core/Live/IOResult.cs b/src/YaNco.Core/Live/IOResult.cs index 4ae655ab..650fb059 100644 --- a/src/YaNco.Core/Live/IOResult.cs +++ b/src/YaNco.Core/Live/IOResult.cs @@ -2,6 +2,9 @@ namespace Dbosoft.YaNco.Live; +/// +/// This is a helper type to handle the result of an RFC call. +/// public static class IOResult { public static Either ResultOrError( diff --git a/src/YaNco.Core/Live/LiveSAPRfcConnectionIO.cs b/src/YaNco.Core/Live/LiveSAPRfcConnectionIO.cs index 848dc541..8c3bb751 100644 --- a/src/YaNco.Core/Live/LiveSAPRfcConnectionIO.cs +++ b/src/YaNco.Core/Live/LiveSAPRfcConnectionIO.cs @@ -1,9 +1,13 @@ using System.Collections.Generic; using Dbosoft.YaNco.Internal; +using Dbosoft.YaNco.Traits; using LanguageExt; namespace Dbosoft.YaNco.Live; +/// +/// This is the implementation of the SAPRfcConnectionIO interface for live connections. +/// public readonly struct LiveSAPRfcConnectionIO : SAPRfcConnectionIO { private readonly Option _logger; diff --git a/src/YaNco.Core/Live/LiveSAPRfcDataIO.cs b/src/YaNco.Core/Live/LiveSAPRfcDataIO.cs index acb1c5e6..125ed81d 100644 --- a/src/YaNco.Core/Live/LiveSAPRfcDataIO.cs +++ b/src/YaNco.Core/Live/LiveSAPRfcDataIO.cs @@ -1,10 +1,14 @@ using Dbosoft.YaNco.Internal; +using Dbosoft.YaNco.Traits; using Dbosoft.YaNco.TypeMapping; using LanguageExt; using System; namespace Dbosoft.YaNco.Live; +/// +/// This is the implementation of the SAPRfcDataIO interface for live connections. +/// public readonly struct LiveSAPRfcDataIO : SAPRfcDataIO { public Option Logger { get; } diff --git a/src/YaNco.Core/Live/LiveSAPRfcFunctionIO.cs b/src/YaNco.Core/Live/LiveSAPRfcFunctionIO.cs index a77c799e..a0a52412 100644 --- a/src/YaNco.Core/Live/LiveSAPRfcFunctionIO.cs +++ b/src/YaNco.Core/Live/LiveSAPRfcFunctionIO.cs @@ -1,9 +1,13 @@ using System; using Dbosoft.YaNco.Internal; +using Dbosoft.YaNco.Traits; using LanguageExt; namespace Dbosoft.YaNco.Live; +/// +/// This is the implementation of the SAPRfcFunctionIO interface for live connections. +/// public readonly struct LiveSAPRfcFunctionIO : SAPRfcFunctionIO { private readonly Option _logger; diff --git a/src/YaNco.Core/Live/LiveSAPRfcServerIO.cs b/src/YaNco.Core/Live/LiveSAPRfcServerIO.cs index 1c089c2b..905bf8b5 100644 --- a/src/YaNco.Core/Live/LiveSAPRfcServerIO.cs +++ b/src/YaNco.Core/Live/LiveSAPRfcServerIO.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; using Dbosoft.YaNco.Internal; +using Dbosoft.YaNco.Traits; using LanguageExt; namespace Dbosoft.YaNco.Live; + public readonly struct LiveSAPRfcServerIO : SAPRfcServerIO { private readonly Option _logger; diff --git a/src/YaNco.Core/Live/SAPRfcRuntime.cs b/src/YaNco.Core/Live/SAPRfcRuntime.cs index 6c8f8a75..90678390 100644 --- a/src/YaNco.Core/Live/SAPRfcRuntime.cs +++ b/src/YaNco.Core/Live/SAPRfcRuntime.cs @@ -1,26 +1,32 @@ using System; using System.Threading; +using Dbosoft.YaNco.Traits; using Dbosoft.YaNco.TypeMapping; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco.Live; +/// +/// This is the default runtime that uses the live IO implementations for SAP RFC +/// public readonly struct SAPRfcRuntime - : HasSAPRfcLogger, - HasSAPRfcData, - HasSAPRfcFunctions, - HasSAPRfcConnection, - HasSAPRfcServer, - HasFieldMapper, - HasEnvRuntimeSettings + : HasSAPRfc, + HasSAPRfcServer, + HasCancel { + + /// + /// Static default runtime that can be used to create a runtime with default settings + /// public static SAPRfcRuntime Default => New( new CancellationTokenSource(), new SAPRfcRuntimeSettings(null, RfcMappingConfigurer.CreateDefaultFieldMapper(), new RfcRuntimeOptions())); private readonly SAPRfcRuntimeEnv _env; + /// /// Constructor /// @@ -39,8 +45,14 @@ private SAPRfcRuntime(SAPRfcRuntimeEnv env) => /// public static SAPRfcRuntime New(CancellationTokenSource cancellationTokenSource, SAPRfcRuntimeSettings settings) => new(new SAPRfcRuntimeEnv(cancellationTokenSource, settings)); + + + /// + /// Creates a new runtime by using the default settings for the environment + /// public static SAPRfcRuntime New() => new(new SAPRfcRuntimeEnv( - new CancellationTokenSource(), Default.Env.Settings + new CancellationTokenSource(), new SAPRfcRuntimeSettings(Default.Env.Settings.Logger, + Default.Env.Settings.FieldMapper, Default.Env.Settings.Options) )); @@ -65,9 +77,10 @@ public static SAPRfcRuntime New(CancellationTokenSource cancellationTokenSource, public CancellationTokenSource CancellationTokenSource => Env.Source; + public Option Logger => Env.Settings.Logger == null? Option.None : Prelude.Some(Env.Settings.Logger); - private SAPRfcDataIO DataIO => Env.Settings.RfcDataIO ?? new LiveSAPRfcDataIO(Logger, Env.Settings.FieldMapper, Env.Settings.TableOptions); + private SAPRfcDataIO DataIO => Env.Settings.RfcDataIO ?? new LiveSAPRfcDataIO(Logger, Env.Settings.FieldMapper, Env.Settings.Options); private SAPRfcFunctionIO FunctionIO => Env.Settings.RfcFunctionIO ?? new LiveSAPRfcFunctionIO(Logger, DataIO); private SAPRfcConnectionIO ConnectionIO => Env.Settings.RfcConnectionIO ?? new LiveSAPRfcConnectionIO(Logger); private SAPRfcServerIO ServerIO => Env.Settings.RfcServerIO ?? new LiveSAPRfcServerIO(Logger); @@ -87,7 +100,6 @@ public static SAPRfcRuntime New(CancellationTokenSource cancellationTokenSource, public Eff RfcServerEff => Prelude.Eff( rt => rt.ServerIO); - public Eff FieldMapperEff => Prelude.Eff < SAPRfcRuntime, IFieldMapper>( rt => rt.Env.Settings.FieldMapper); } \ No newline at end of file diff --git a/src/YaNco.Core/RfcBuilderBase.cs b/src/YaNco.Core/RfcBuilderBase.cs index 580b7377..51ece4b6 100644 --- a/src/YaNco.Core/RfcBuilderBase.cs +++ b/src/YaNco.Core/RfcBuilderBase.cs @@ -1,13 +1,19 @@ using System; using System.Collections.Generic; using System.Threading; +using Dbosoft.YaNco.Traits; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; -public abstract class RfcBuilderBase where RT : struct, - HasSAPRfcFunctions, HasSAPRfcServer, - HasSAPRfcConnection, HasSAPRfcLogger, HasSAPRfcData +/// +/// Base class for building a RFC clients or servers. +/// +/// The builder type for chaining +/// Runtime type +public abstract class RfcBuilderBase where RT : struct, + HasSAPRfc, HasCancel where TBuilder: RfcBuilderBase { protected TBuilder Self { get; set; } @@ -37,16 +43,15 @@ public TBuilder WithStartProgramCallback(StartProgramDelegate startProgramDelega } /// - /// This method registers a function handler from a + /// This method registers a function handler from a /// /// Name of function /// action to configure function builder /// function handler /// current instance for chaining /// - /// The metadata of the function is build in the . This allows to register + /// The metadata of the function is build in the . This allows to register /// any kind of function. - /// To register a known function use the signature with function name /// Function handlers are registered process wide (in the SAP NW RFC Library) and mapped to backend system id. /// Multiple registrations of same function and same backend id will therefore have no effect. /// @@ -61,14 +66,6 @@ public TBuilder WithFunctionHandler(string functionName, private Action> _configureRuntime = _ => { }; - /// - /// Registers a action to configure the - /// - /// action with - /// current instance for chaining. - /// - /// Multiple calls of this method will override the previous configuration action. - /// protected TBuilder ConfigureRuntimeInternal(Action> configure) { lock (RfcBuilderSync.SyncObject) diff --git a/src/YaNco.Core/RfcContextRT.cs b/src/YaNco.Core/RfcContextRT.cs index 92933ef1..41c02fdf 100644 --- a/src/YaNco.Core/RfcContextRT.cs +++ b/src/YaNco.Core/RfcContextRT.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading; +using Dbosoft.YaNco.Traits; using LanguageExt; using LanguageExt.Effects.Traits; @@ -9,7 +10,7 @@ namespace Dbosoft.YaNco; public class RfcContext : IRfcContext - where RT : struct, HasSAPRfcData, HasCancel + where RT : struct, HasSAPRfc, HasCancel { private readonly Aff _connectionEffect; private Option _openedConnection; @@ -51,18 +52,19 @@ from connection in GetConnection() from res in SAPRfc.invokeFunction(connection, function) select res; - + /// public Aff Ping() => from connection in GetConnection() from res in SAPRfc.ping(connection) select res; - + /// public Aff CreateFunction(string name) => from connection in GetConnection() from res in SAPRfc.createFunction(connection,name) select res; + /// public Aff CallFunction( string functionName, Func, Either> Input, @@ -71,7 +73,7 @@ from connection in GetConnection() from res in SAPRfc.callFunction(connection, functionName, Input, Output) select res; - + /// public Aff CallFunction( string functionName, Func, Either> Output) => @@ -79,14 +81,14 @@ from connection in GetConnection() from res in SAPRfc.callFunction(connection, functionName, Output) select res; - + /// public Aff InvokeFunction( string functionName) => from connection in GetConnection() from res in SAPRfc.invokeFunction(connection, functionName) select res; - + /// public Aff InvokeFunction( string functionName, Func, Either> Input) => @@ -94,19 +96,19 @@ from connection in GetConnection() from res in SAPRfc.invokeFunction(connection, functionName, Input) select res; - + /// public Aff Commit() => from connection in GetConnection() from res in SAPRfc.commit(connection) select res; - + /// public Aff CommitAndWait() => from connection in GetConnection() from res in SAPRfc.commitAndWait(connection) select res; - + /// public Aff Rollback() => from connection in GetConnection() from res in SAPRfc.rollback(connection) @@ -122,6 +124,7 @@ protected virtual void Dispose(bool disposing) _openedConnection = Option.None; } + /// public void Dispose() { Dispose(true); diff --git a/src/YaNco.Core/RfcContextRuntimeAccess.cs b/src/YaNco.Core/RfcContextRuntimeAccess.cs index 762866f8..46db9222 100644 --- a/src/YaNco.Core/RfcContextRuntimeAccess.cs +++ b/src/YaNco.Core/RfcContextRuntimeAccess.cs @@ -9,24 +9,25 @@ namespace Dbosoft.YaNco; public static class RfcContextRuntimeAccess { /// - /// This method is a helper method to run a IO effect from a . - /// If you have to use this method please consider migrating to - /// where you can access the runtime directly via the method. - /// The IO effect is bound to the of the connection behind the . - /// /// + /// This method is a helper method to execute an IO effect from a . + /// The IO effect is executed with the of the connection behind the . + /// + /// + /// If you must use this method, please consider migrating to . + /// where you can access the runtime directly via the method without any side effects. + /// /// The result type /// context to be used /// function to construct the IO effect. - /// that can be used to t run IO effects within the - public static EitherAsync RunIO(this IRfcContext context, Func> ioFunc) + /// A with any error as left state and as right state. + public static EitherAsync RunIO(this IRfcContext context, Func> ioFunc) { // the runtime if RfcContext is always a SAPRfcRuntime return context.GetConnection().Bind(c => { var runtime = (SAPRfcRuntime)c.ConnectionRuntime; - - return ioFunc().ToEither(runtime); + return ioFunc(c).ToEither(runtime); }); } @@ -42,7 +43,7 @@ public static EitherAsync RunIO(this IRfcContext context, Func /// context to be used /// function to construct the IO effect. /// A with any error as left state and as right state. - public static EitherAsync RunIO(this IRfcContext context, Func> ioFunc) + public static EitherAsync RunIO(this IRfcContext context, Func> ioFunc) { // the runtime if RfcContext is always a SAPRfcRuntime @@ -50,7 +51,7 @@ public static EitherAsync RunIO(this IRfcContext context, Func { var runtime = (SAPRfcRuntime)c.ConnectionRuntime; - return ioFunc().ToEither(runtime).ToAsync(); + return ioFunc(c).ToEither(runtime).ToAsync(); }); } } \ No newline at end of file diff --git a/src/YaNco.Core/RfcRuntime.cs b/src/YaNco.Core/RfcRuntime.cs index e4cbf394..c33d09e4 100644 --- a/src/YaNco.Core/RfcRuntime.cs +++ b/src/YaNco.Core/RfcRuntime.cs @@ -2,8 +2,10 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Dbosoft.YaNco.Live; +using Dbosoft.YaNco.Traits; using Dbosoft.YaNco.TypeMapping; using LanguageExt; +using LanguageExt.Effects.Traits; // ReSharper disable UnusedMember.Global @@ -22,9 +24,7 @@ public RfcRuntime(SAPRfcRuntime runtime) : base(runtime) [Obsolete(Deprecations.RfcRuntime)] public class RfcRuntime : IRfcRuntime where RT : struct, - HasSAPRfcServer, HasSAPRfcFunctions, HasSAPRfcConnection, - HasSAPRfcLogger, HasSAPRfcData, - HasEnvRuntimeSettings + HasSAPRfcServer, HasSAPRfc, HasCancel { private readonly RT _runtime; @@ -33,7 +33,7 @@ public RfcRuntime(RT runtime) _runtime = runtime; } - public RfcRuntimeOptions Options => _runtime.Env.Settings.TableOptions; + public RfcRuntimeOptions Options => _runtime.Env.Settings.Options; public IFieldMapper FieldMapper => _runtime.Env.Settings.FieldMapper; public Option Logger => _runtime.Env.Settings.Logger != null ? Prelude.Some(_runtime.Env.Settings.Logger) : Option.None; diff --git a/src/YaNco.Core/RfcServer.cs b/src/YaNco.Core/RfcServer.cs index 9aaa8735..ff1d0b60 100644 --- a/src/YaNco.Core/RfcServer.cs +++ b/src/YaNco.Core/RfcServer.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using Dbosoft.Functional; +using Dbosoft.YaNco.Traits; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; public class RfcServer : IRfcServer - where RT : struct, HasSAPRfcServer, HasSAPRfcLogger + where RT : struct, HasSAPRfcServer, HasSAPRfc, HasCancel { private readonly IAgent> _stateAgent; diff --git a/src/YaNco.Core/RfcServerClientConfigurer.cs b/src/YaNco.Core/RfcServerClientConfigurer.cs index 7fdab8ef..537591b3 100644 --- a/src/YaNco.Core/RfcServerClientConfigurer.cs +++ b/src/YaNco.Core/RfcServerClientConfigurer.cs @@ -1,10 +1,12 @@ using System; +using Dbosoft.YaNco.Traits; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; -public class RfcServerClientConfigurer where RT : struct, - HasSAPRfcLogger, HasSAPRfcData, HasSAPRfcServer, HasSAPRfcFunctions, HasSAPRfcConnection, HasEnvRuntimeSettings +public class RfcServerClientConfigurer where RT : struct, + HasSAPRfcServer, HasSAPRfc, HasCancel { private readonly ConnectionBuilder _builder; diff --git a/src/YaNco.Core/RfcServerContext.cs b/src/YaNco.Core/RfcServerContext.cs index cd63478f..6d24ca95 100644 --- a/src/YaNco.Core/RfcServerContext.cs +++ b/src/YaNco.Core/RfcServerContext.cs @@ -1,10 +1,12 @@ using System; +using Dbosoft.YaNco.Traits; using LanguageExt; using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; -internal class RfcServerContext : IRfcContext where RT : struct, HasSAPRfcData, HasCancel +internal class RfcServerContext : IRfcContext where RT : struct, + HasSAPRfc, HasCancel { private readonly IRfcServer _rfcServer; private IRfcContext _currentContext; diff --git a/src/YaNco.Core/SAPRfc.cs b/src/YaNco.Core/SAPRfc.cs index a99413a7..d4b2cfd5 100644 --- a/src/YaNco.Core/SAPRfc.cs +++ b/src/YaNco.Core/SAPRfc.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using Dbosoft.YaNco.Traits; using Dbosoft.YaNco.TypeMapping; using JetBrains.Annotations; using LanguageExt; @@ -9,13 +11,48 @@ namespace Dbosoft.YaNco; #pragma warning disable IDE1006 [PublicAPI] -public static class SAPRfc where RT : struct, HasCancel, HasSAPRfcData +public static class SAPRfc where RT : struct, HasSAPRfc, HasCancel { + /// + /// Builds a SAP RFC connection from connection parameters. + /// + /// parameters of the connection + /// a action to configure the client + /// The async effect to open the client connection. + public static Eff> buildClient(IDictionary connectionParam, + Action> configure = null) + { + return Prelude.Eff(() => + { + var builder = new ConnectionBuilder(connectionParam); + configure?.Invoke(builder); + return builder.Build(); + + }); + + } + + /// + /// This methods wraps a connection effect and runs the effect to be executed with the connection. + /// The connection is disposed after the function is executed. + /// + /// + /// that opens the connection. + /// + /// public static Aff useConnection(Aff connectionEffect, Func> mapFunc) { return Prelude.use(connectionEffect, mapFunc); } + /// + /// this methods creates a RFC context from a connection effect + /// and runs the effect that should be executed with the context. + /// + /// + /// + /// + /// public static Aff useContext(Aff connectionEffect, Func, Aff> mapFunc) { return Prelude.use(Prelude.Eff(() => new RfcContext(connectionEffect)), mapFunc); @@ -24,6 +61,7 @@ public static Aff useContext(Aff connectionEffect, /// /// Creates a RFC function from function name. /// + /// the RFC connection /// Name of the function as defined in SAP. /// A with any error as left state and function as right state. public static Aff createFunction(IConnection connection, string functionName) @@ -35,6 +73,7 @@ public static Aff createFunction(IConnection connection, string f /// /// Calls the function specified in parameter function /// + /// the RFC connection /// The function to be invoked. /// A with any error as left state and as right state. public static Aff invokeFunction(IConnection connection, IFunction function) @@ -56,6 +95,7 @@ from _ in connection.InvokeFunction(function, ct).ToAff(l => l) /// to any kind of type. The output type is returned after processing the ABAP function. /// /// + /// the RFC connection /// /// /// name of the function as defined in SAP backend @@ -83,6 +123,7 @@ from output in Output(Prelude.Right(func)).ToAff(l => l) /// to any kind of type. The output type is returned after processing the ABAP function. /// /// + /// the RFC connection /// /// ABAP function name /// Output function lifted in either monad. @@ -102,6 +143,7 @@ from output in Output(Prelude.Right(func)).ToAff(l => l) /// This method calls a SAP RFM without input and output. /// /// ABAP function name + /// the RFC connection /// Unit public static Aff invokeFunction( IConnection connection, @@ -123,6 +165,7 @@ from _ in invokeFunction(connection, func) /// to any kind of type. The input type itself is not used any more /// after calling the input mapping. /// + /// the RFC connection /// ABAP function name /// Input function lifted in either monad. /// Unit @@ -188,6 +231,12 @@ from _ in connection.Rollback(ct).ToAff(l => l) } + /// + /// This method can be used to convert a to a .NET type. + /// + /// + /// + /// public static Eff getValue(AbapValue abapValue) { return from dataIO in default(RT).RfcDataEff @@ -196,6 +245,14 @@ from result in dataIO.GetValue(abapValue).ToEff(l => l) } + /// + /// This method can be used to convert a .NET type to a . + /// + /// + /// + /// Field info for the rfc field. This can be obtained from a fields metadata + /// and type descriptions. + /// public static Eff setValue(TIn value, RfcFieldInfo fieldInfo) { return from dataIO in default(RT).RfcDataEff diff --git a/src/YaNco.Core/SAPRfcServer.cs b/src/YaNco.Core/SAPRfcServer.cs index fb573abb..7d9bc7fe 100644 --- a/src/YaNco.Core/SAPRfcServer.cs +++ b/src/YaNco.Core/SAPRfcServer.cs @@ -1,17 +1,33 @@ using System; +using System.Collections.Generic; +using Dbosoft.YaNco.Traits; using LanguageExt; +using LanguageExt.Effects.Traits; // ReSharper disable InconsistentNaming namespace Dbosoft.YaNco; public static class SAPRfcServer - where RT : struct, HasSAPRfcServer + where RT : struct, HasSAPRfcServer, HasSAPRfc, HasCancel { public static Eff getServerAttributes(IRfcHandle handle) { return default(RT).RfcServerEff.Bind(io => io.GetServerCallContext(handle).ToEff(l => l)); } + public static Eff>> buildServer(IDictionary serverParam, + Action> configure) + { + return Prelude.Eff(() => + { + var builder = new ServerBuilder(serverParam); + configure(builder); + return builder.Build(); + + }); + + } + public static Aff useServer(Aff> serverEffect, Func, Aff> ma) { var startAff = serverEffect.Bind(s => s.Start().Map(_ => s).ToAff(l=>l)); diff --git a/src/YaNco.Core/ServerBuilder.cs b/src/YaNco.Core/ServerBuilder.cs index cd68d978..0ebcceeb 100644 --- a/src/YaNco.Core/ServerBuilder.cs +++ b/src/YaNco.Core/ServerBuilder.cs @@ -2,10 +2,21 @@ using System.Collections.Generic; using System.Threading; using Dbosoft.YaNco.Live; +using Dbosoft.YaNco.Traits; +using JetBrains.Annotations; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; +/// +/// This class is used to build server connections to a SAP ABAP backend. +/// +/// The non-generic version of this class uses the build-in runtime . +/// To use a custom runtime, use instead. +/// +/// +[PublicAPI] public class ServerBuilder : ServerBuilderBase { public ServerBuilder(IDictionary serverParam) : base(serverParam) @@ -13,9 +24,9 @@ public ServerBuilder(IDictionary serverParam) : base(serverParam } /// - /// Registers a action to configure the + /// Registers a action to configure build-in /// - /// action with + /// action with /// current instance for chaining. /// /// Multiple calls of this method will override the previous configuration action. @@ -23,6 +34,16 @@ public ServerBuilder(IDictionary serverParam) : base(serverParam public ServerBuilder ConfigureRuntime(Action> configure) => ConfigureRuntimeInternal(configure); + /// + /// This methods builds the + /// with a build-in runtime of type + /// + /// + /// The runtime is created with the configuration actions registered with + /// If you want to use a custom runtime, use instead. + /// Multiple calls of this method will return the same server. + /// + /// with the public new EitherAsync> Build() { @@ -30,12 +51,26 @@ public ServerBuilder ConfigureRuntime(Action SAPRfcRuntime.New(env.Source, env.Settings)); return base.Build().ToEither(runtime); } -} + /// + /// This methods builds the async effect to create the + /// + /// + /// The runtime is created with the configuration actions registered with + /// If you want to use a custom runtime, use instead. + /// Multiple calls of this method will return the same effect. + /// + /// with the + public Aff> BuildIO() + => base.Build(); +} +/// +/// This method is used to build a server for RFC communication with a SAP ABAP backend. +/// +/// The runtime to use public class ServerBuilder : ServerBuilderBase, RT> - where RT : struct, HasSAPRfcServer, - HasSAPRfcLogger, HasSAPRfcData, HasSAPRfcFunctions, HasSAPRfcConnection, HasEnvRuntimeSettings + where RT : struct, HasSAPRfcServer, HasSAPRfc, HasCancel { public ServerBuilder(IDictionary serverParam) : base(serverParam) { diff --git a/src/YaNco.Core/ServerBuilderBase.cs b/src/YaNco.Core/ServerBuilderBase.cs index 0ab1057f..be2fb1de 100644 --- a/src/YaNco.Core/ServerBuilderBase.cs +++ b/src/YaNco.Core/ServerBuilderBase.cs @@ -1,18 +1,21 @@ using System; using System.Collections.Generic; using System.Linq; +using Dbosoft.YaNco.Traits; using JetBrains.Annotations; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco; /// -/// This class is used to configure a SAP RFC server +/// Base class for building a /// +/// The builder type for chaining +/// Runtime type [PublicAPI] public class ServerBuilderBase : RfcBuilderBase - where RT : struct, HasSAPRfcServer, - HasSAPRfcLogger, HasSAPRfcData, HasSAPRfcFunctions, HasSAPRfcConnection, HasEnvRuntimeSettings + where RT : struct, HasSAPRfcServer, HasSAPRfc, HasCancel where TBuilder: ServerBuilderBase { @@ -30,7 +33,7 @@ private Func, RT, Eff>> private ITransactionalRfcHandler _transactionalRfcHandler; /// - /// Creates a new with the connection parameters supplied + /// Creates a new with the connection parameters supplied /// /// /// @@ -47,10 +50,10 @@ public ServerBuilderBase(IDictionary serverParam) } /// - /// Use a alternative factory method to create the . + /// Use a alternative factory method to create the . /// /// factory method - /// current instance for chaining. for chaining /// The default implementation call . /// public TBuilder UseFactory(Func, RT, Eff>> factory) @@ -60,11 +63,11 @@ public TBuilder UseFactory(Func, RT, Eff - /// Configures the client connection of the + /// Configures the client connection of the /// /// connection parameters /// action to configure connection - /// current instance for chaining. + /// for chaining public TBuilder WithClientConnection( IDictionary connectionParams, Action> configure) { @@ -74,16 +77,17 @@ public TBuilder WithClientConnection( } /// - /// Adds a existing client connection factory as client connection of + /// Adds a existing client connection factory as client connection of /// - /// function of connection factory + /// connection IO effect to use /// current instance for chaining. - /// This method signature should only be used in special cases. - /// The created function may have a different or other settings that - /// are not configured automatically on . + /// This method signature should only be used carefully. + /// The created function may have a different runtime instance or other settings that + /// are not configured automatically on . /// Consider using the configured client connection with - /// + /// /// + /// for chaining public TBuilder WithClientConnection( Aff connectionEffect) { @@ -95,13 +99,20 @@ public TBuilder WithClientConnection( /// Adds a transaction handler instance and enables transactional RFC for the server. /// /// - /// + /// for chaining public TBuilder WithTransactionalRfc(ITransactionalRfcHandler transactionalRfcHandler) { _transactionalRfcHandler = transactionalRfcHandler; return (TBuilder)this; } + /// + /// This methods builds the async effect to create the + /// + /// + /// Multiple calls of this method will return the same effect. + /// + /// with the public Aff> Build() { if (_buildServer != null) @@ -116,8 +127,6 @@ public Aff> Build() //take control of registration made by clients clientBuilder.WithFunctionRegistration(_functionRegistration); - //take runtime of client - //clientBuilder.ConfigureRuntime(cfg => cfg.UseFactory((l, m, o) => runtime)); _configureServerClient(new RfcServerClientConfigurer(clientBuilder)); @@ -144,10 +153,10 @@ from server in _serverFactory(_serverParam, rt) return Prelude.SuccessEff(server); return serverIO.AddTransactionHandlers(_systemId, - (handle, tid) => _transactionalRfcHandler.OnCheck(rt, handle, tid), - (handle, tid) => _transactionalRfcHandler.OnCommit(rt, handle, tid), - (handle, tid) => _transactionalRfcHandler.OnRollback(rt, handle, tid), - (handle, tid) => _transactionalRfcHandler.OnConfirm(rt, handle, tid) + (handle, tid) => RunTHandler(rt,"CHECK", ()=> _transactionalRfcHandler.OnCheck(handle, tid)), + (handle, tid) => RunTHandler(rt, "COMMIT", () => _transactionalRfcHandler.OnCommit(handle, tid)), + (handle, tid) => RunTHandler(rt, "ROLLBACK", () => _transactionalRfcHandler.OnRollback(handle, tid)), + (handle, tid) => RunTHandler(rt, "CONFIRM", () => _transactionalRfcHandler.OnConfirm(handle, tid)) ).Map(holder => { server.AddReferences(new[] { holder }); @@ -163,10 +172,18 @@ from server in _serverFactory(_serverParam, rt) _buildServer = server; return server; }) - select server; - + select server; } + private static RfcRc RunTHandler(RT runtime, string name, Func> handler) => + (from oLog in default(RT).RfcLoggerEff + from rc in handler().MapFail( e => + { + oLog.IfSome(logger => logger.LogError($"tRFC handler {name} failed", e)); + return e; + }) + select rc) + .Run(runtime).IfFail(RfcRc.RFC_EXTERNAL_FAILURE); private Eff> RegisterFunctionHandlers(IRfcServer server) { diff --git a/src/YaNco.Core/Structure.cs b/src/YaNco.Core/Structure.cs index d923a0c3..04d73725 100644 --- a/src/YaNco.Core/Structure.cs +++ b/src/YaNco.Core/Structure.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using Dbosoft.YaNco.Traits; using Dbosoft.YaNco.TypeMapping; using LanguageExt; diff --git a/src/YaNco.Core/Table.cs b/src/YaNco.Core/Table.cs index 542d88d2..d1948030 100644 --- a/src/YaNco.Core/Table.cs +++ b/src/YaNco.Core/Table.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Dbosoft.YaNco.Traits; using LanguageExt; namespace Dbosoft.YaNco; diff --git a/src/YaNco.Core/TableRowEnumerator.cs b/src/YaNco.Core/TableRowEnumerator.cs index 28241a14..455a4b01 100644 --- a/src/YaNco.Core/TableRowEnumerator.cs +++ b/src/YaNco.Core/TableRowEnumerator.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using Dbosoft.YaNco.Traits; using LanguageExt; namespace Dbosoft.YaNco; diff --git a/src/YaNco.Core/Test/TestSAPRfcRuntime.cs b/src/YaNco.Core/Test/TestSAPRfcRuntime.cs index b6ddc33a..a61ef07d 100644 --- a/src/YaNco.Core/Test/TestSAPRfcRuntime.cs +++ b/src/YaNco.Core/Test/TestSAPRfcRuntime.cs @@ -1,19 +1,17 @@ using System; using System.Threading; +using Dbosoft.YaNco.Traits; using Dbosoft.YaNco.TypeMapping; using LanguageExt; +using LanguageExt.Effects.Traits; namespace Dbosoft.YaNco.Test; public readonly struct TestSAPRfcRuntime - : HasSAPRfcLogger, - HasSAPRfcData, - HasSAPRfcFunctions, - HasSAPRfcConnection, - HasSAPRfcServer, - HasFieldMapper, - HasEnvRuntimeSettings + : HasSAPRfcServer, + HasSAPRfc, + HasCancel { private readonly SAPRfcRuntimeEnv _env; /// diff --git a/src/YaNco.Core/TypeDescriptionDataContainer.cs b/src/YaNco.Core/TypeDescriptionDataContainer.cs index 3acf22ba..41057670 100644 --- a/src/YaNco.Core/TypeDescriptionDataContainer.cs +++ b/src/YaNco.Core/TypeDescriptionDataContainer.cs @@ -1,4 +1,5 @@ -using LanguageExt; +using Dbosoft.YaNco.Traits; +using LanguageExt; namespace Dbosoft.YaNco; diff --git a/src/YaNco.Core/TypeMapping/ByteValueConverter.cs b/src/YaNco.Core/TypeMapping/ByteValueConverter.cs index 21efdcd1..70149a33 100644 --- a/src/YaNco.Core/TypeMapping/ByteValueConverter.cs +++ b/src/YaNco.Core/TypeMapping/ByteValueConverter.cs @@ -35,14 +35,11 @@ public bool CanConvertTo(RfcType rfcType) private static bool IsSupportedRfcType(RfcType rfcType) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (rfcType) + return rfcType switch { - case RfcType.BYTE: - case RfcType.XSTRING: - return true; - default: - return false; - } - + RfcType.BYTE => true, + RfcType.XSTRING => true, + _ => false + }; } } \ No newline at end of file diff --git a/src/YaNco.Core/TypeMapping/CachingConverterResolver.cs b/src/YaNco.Core/TypeMapping/CachingConverterResolver.cs index c96ec38c..1c6d68bb 100644 --- a/src/YaNco.Core/TypeMapping/CachingConverterResolver.cs +++ b/src/YaNco.Core/TypeMapping/CachingConverterResolver.cs @@ -20,17 +20,22 @@ public CachingConverterResolver(IRfcConverterResolver decoratedResolver) public IEnumerable> GetToRfcConverters(RfcType rfcType) { var sourceType = typeof(T); - var key = $"{rfcType}_{sourceType}"; + var key = $"{rfcType}_{sourceType.AssemblyQualifiedName}"; - if (!_toRfcConverters.ContainsKey(key)) + if (!_toRfcConverters.TryGetValue(key, out var entry)) { var converters = _decoratedResolver.GetToRfcConverters(rfcType).ToArray(); - _toRfcConverters.Add(key, converters.Length == 0 ? null : converters); - + entry = converters.Length == 0 ? null : converters; + try + { + _toRfcConverters.Add(key, entry); + } + catch (Exception) + { + // ignored + } } - var entry = _toRfcConverters[key]; - if (entry != null) return (IEnumerable>)entry; return Array.Empty>(); @@ -41,16 +46,22 @@ public IEnumerable> GetToRfcConverters(RfcType rfcTy public IEnumerable> GetFromRfcConverters(RfcType rfcType, Type abapValueType) { var targetType = typeof(T); - var key = $"{rfcType}_{targetType}"; + var key = $"{rfcType}_{targetType.AssemblyQualifiedName}"; - if (!_fromRfcConverters.ContainsKey(key)) + if (!_fromRfcConverters.TryGetValue(key, out var entry)) { var converters = _decoratedResolver.GetFromRfcConverters(rfcType, abapValueType).ToArray(); - _fromRfcConverters.Add(key, converters.Length == 0 ? null : converters); + entry = converters.Length == 0 ? null : converters; + try + { + _fromRfcConverters.Add(key, entry); + } + catch (Exception) + { + // ignored + } } - var entry = _fromRfcConverters[key]; - if (entry != null) return (IEnumerable>)entry; return Array.Empty>(); diff --git a/src/YaNco.Core/TypeMapping/DateTimeValueConverter.cs b/src/YaNco.Core/TypeMapping/DateTimeValueConverter.cs index 60a76e7b..32801373 100644 --- a/src/YaNco.Core/TypeMapping/DateTimeValueConverter.cs +++ b/src/YaNco.Core/TypeMapping/DateTimeValueConverter.cs @@ -39,15 +39,12 @@ public bool CanConvertFrom(RfcType rfcType) private static bool IsSupportedRfcType(RfcType rfcType) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (rfcType) + return rfcType switch { - case RfcType.DATE: - case RfcType.TIME: - return true; - default: - return false; - } - + RfcType.DATE => true, + RfcType.TIME => true, + _ => false + }; } public Try ConvertTo(AbapValue abapValue) diff --git a/src/YaNco.Core/TypeMapping/DefaultFieldMapper.cs b/src/YaNco.Core/TypeMapping/DefaultFieldMapper.cs index 4b80f1c9..404145ad 100644 --- a/src/YaNco.Core/TypeMapping/DefaultFieldMapper.cs +++ b/src/YaNco.Core/TypeMapping/DefaultFieldMapper.cs @@ -3,6 +3,9 @@ namespace Dbosoft.YaNco.TypeMapping; +/// +/// THis is the default field mapper that is used by to map fields from and to SAP RFC +/// public class DefaultFieldMapper : IFieldMapper { private readonly IRfcConverterResolver _converterResolver; @@ -45,50 +48,50 @@ public Either GetField(FieldMappingContext context) return context.Apply(_ => { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (context.FieldInfo.Type) + // ReSharper disable once SwitchExpressionHandlesSomeKnownEnumValuesWithExceptionInDefault + return context.FieldInfo.Type switch { - case RfcType.DATE: - return context.IO.GetDateString(context.Handle, context.FieldInfo.Name).Map(v => - (AbapValue) new AbapStringValue(context.FieldInfo, v)); - case RfcType.TIME: - return context.IO.GetTimeString(context.Handle, context.FieldInfo.Name).Map(v => - (AbapValue) new AbapStringValue(context.FieldInfo, v)); - case RfcType.CHAR: - case RfcType.NUM: - case RfcType.STRING: - case RfcType.BCD: - case RfcType.FLOAT: - case RfcType.DECF16: - case RfcType.DECF34: - return context.IO.GetString(context.Handle, context.FieldInfo.Name).Map(v => - (AbapValue) new AbapStringValue(context.FieldInfo, v)); - case RfcType.INT: - case RfcType.INT2: - case RfcType.INT1: - return context.IO.GetInt(context.Handle, context.FieldInfo.Name).Map(v => - (AbapValue) new AbapIntValue(context.FieldInfo, v)); - case RfcType.INT8: - return context.IO.GetLong(context.Handle, context.FieldInfo.Name).Map(v => - (AbapValue) new AbapLongValue(context.FieldInfo, v)); - case RfcType.BYTE: - case RfcType.XSTRING: - return context.IO.GetBytes(context.Handle, context.FieldInfo.Name).Map(v => - (AbapValue) new AbapByteValue(context.FieldInfo, v)); - case RfcType.STRUCTURE: - return context.IO.GetStructure(context.Handle, context.FieldInfo.Name) - .Map(handle => (IStructure)new Structure(handle, context.IO)) - .Bind(s => s.ToDictionary()) - .Map(d => (AbapValue)new AbapStructureValues(context.FieldInfo, d)); - case RfcType.TABLE: - return context.IO.GetTable(context.Handle, context.FieldInfo.Name) - .Map(handle => (ITable)new Table(handle, context.IO)) - .MapStructure(d => d.ToDictionary()) - .Map(tr => (AbapValue)new AbapTableValues(context.FieldInfo, tr)); - - default: - throw new NotSupportedException( - $"Reading a field of RfcType {context.FieldInfo.Type} is not supported for this method."); - } + RfcType.DATE => context.IO.GetDateString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.TIME => context.IO.GetTimeString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.CHAR => context.IO.GetString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.NUM => context.IO.GetString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.STRING => context.IO.GetString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.BCD => context.IO.GetString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.FLOAT => context.IO.GetString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.DECF16 => context.IO.GetString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.DECF34 => context.IO.GetString(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapStringValue(context.FieldInfo, v)), + RfcType.INT => context.IO.GetInt(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapIntValue(context.FieldInfo, v)), + RfcType.INT2 => context.IO.GetInt(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapIntValue(context.FieldInfo, v)), + RfcType.INT1 => context.IO.GetInt(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapIntValue(context.FieldInfo, v)), + RfcType.INT8 => context.IO.GetLong(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapLongValue(context.FieldInfo, v)), + RfcType.BYTE => context.IO.GetBytes(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapByteValue(context.FieldInfo, v)), + RfcType.XSTRING => context.IO.GetBytes(context.Handle, context.FieldInfo.Name) + .Map(v => (AbapValue)new AbapByteValue(context.FieldInfo, v)), + RfcType.STRUCTURE => context.IO.GetStructure(context.Handle, context.FieldInfo.Name) + .Map(handle => (IStructure)new Structure(handle, context.IO)) + .Bind(s => s.ToDictionary()) + .Map(d => (AbapValue)new AbapStructureValues(context.FieldInfo, d)), + RfcType.TABLE => context.IO.GetTable(context.Handle, context.FieldInfo.Name) + .Map(handle => (ITable)new Table(handle, context.IO)) + .MapStructure(d => d.ToDictionary()) + .Map(tr => (AbapValue)new AbapTableValues(context.FieldInfo, tr)), + _ => throw new NotSupportedException( + $"Reading a field of RfcType {context.FieldInfo.Type} is not supported for this method.") + }; }).Bind(FromAbapValue); } diff --git a/src/YaNco.Core/TypeMapping/DefaultFromAbapValueConverter.cs b/src/YaNco.Core/TypeMapping/DefaultFromAbapValueConverter.cs index f3bab2d5..9f67b8a3 100644 --- a/src/YaNco.Core/TypeMapping/DefaultFromAbapValueConverter.cs +++ b/src/YaNco.Core/TypeMapping/DefaultFromAbapValueConverter.cs @@ -14,25 +14,23 @@ public Try ConvertTo(AbapValue abapValue) public bool CanConvertTo(RfcType rfcType) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (rfcType) + return rfcType switch { - case RfcType.CHAR: - case RfcType.DATE: - case RfcType.BCD: - case RfcType.TIME: - case RfcType.BYTE: - case RfcType.NUM: - case RfcType.FLOAT: - case RfcType.INT: - case RfcType.INT2: - case RfcType.INT1: - case RfcType.DECF16: - case RfcType.DECF34: - case RfcType.STRING: - case RfcType.INT8: - return true; - default: - return false; - } + RfcType.CHAR => true, + RfcType.DATE => true, + RfcType.BCD => true, + RfcType.TIME => true, + RfcType.BYTE => true, + RfcType.NUM => true, + RfcType.FLOAT => true, + RfcType.INT => true, + RfcType.INT2 => true, + RfcType.INT1 => true, + RfcType.DECF16 => true, + RfcType.DECF34 => true, + RfcType.STRING => true, + RfcType.INT8 => true, + _ => false + }; } } \ No newline at end of file diff --git a/src/YaNco.Core/TypeMapping/IntValueConverter.cs b/src/YaNco.Core/TypeMapping/IntValueConverter.cs index 72c71ae7..a17b168a 100644 --- a/src/YaNco.Core/TypeMapping/IntValueConverter.cs +++ b/src/YaNco.Core/TypeMapping/IntValueConverter.cs @@ -27,15 +27,12 @@ public bool CanConvertFrom(RfcType rfcType) private static bool IsSupportedRfcType(RfcType rfcType) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (rfcType) + return rfcType switch { - case RfcType.INT: - case RfcType.INT2: - case RfcType.INT1: - return true; - default: - return false; - } - + RfcType.INT => true, + RfcType.INT2 => true, + RfcType.INT1 => true, + _ => false + }; } } \ No newline at end of file diff --git a/src/YaNco.Core/TypeMapping/StringValueConverter.cs b/src/YaNco.Core/TypeMapping/StringValueConverter.cs index f93fcb87..e994f929 100644 --- a/src/YaNco.Core/TypeMapping/StringValueConverter.cs +++ b/src/YaNco.Core/TypeMapping/StringValueConverter.cs @@ -37,19 +37,16 @@ public bool CanConvertFrom(RfcType rfcType) private static bool IsSupportedRfcType(RfcType rfcType) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault - switch (rfcType) + return rfcType switch { - case RfcType.CHAR: - case RfcType.NUM: - case RfcType.BCD: - case RfcType.FLOAT: - case RfcType.DECF16: - case RfcType.DECF34: - case RfcType.STRING: - return true; - default: - return false; - } - + RfcType.CHAR => true, + RfcType.NUM => true, + RfcType.BCD => true, + RfcType.FLOAT => true, + RfcType.DECF16 => true, + RfcType.DECF34 => true, + RfcType.STRING => true, + _ => false + }; } } \ No newline at end of file diff --git a/src/YaNco.Primitives/RfcError.cs b/src/YaNco.Primitives/RfcError.cs index d34d1f32..f11656ca 100644 --- a/src/YaNco.Primitives/RfcError.cs +++ b/src/YaNco.Primitives/RfcError.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System.Text; +using JetBrains.Annotations; using LanguageExt; using LanguageExt.Common; @@ -44,5 +45,14 @@ public static RfcError New(Error error) return new RfcError(RfcErrorInfo.Error(error.Message), error); } - public Error AsError => this; + public Error AsError => this; + + protected override bool PrintMembers(StringBuilder builder) + { + builder.Append($"Code: {Code},"); + builder.Append($"Message: {Message}"); + + + return true; + } } \ No newline at end of file diff --git a/test/SAPSystemTests/Program.cs b/test/SAPSystemTests/Program.cs index c060c7ce..6da1de9a 100644 --- a/test/SAPSystemTests/Program.cs +++ b/test/SAPSystemTests/Program.cs @@ -85,6 +85,11 @@ await context.GetConnection().Bind(c => c.GetAttributes()) // call server function _ = await context.CallFunctionOneWay("ZYANCO_IT_3", f => f).ToEither(); + // functional bridge test + _ = await context.RunIO( c=> + from call in invokeFunction(c, "ZYANCO_IT_3") + select call).IfLeft(l => l.Throw()); + await RunIntegrationTests(context); } @@ -140,22 +145,25 @@ from userName in context.CallFunction("BAPI_USER_GET_DETAIL", f => result = await call.Run(SAPRfcRuntime.Default); Console.WriteLine("call_getUsername_with_context: " + result ); + // test that performs field mapping of a ABAPValue + var withFieldMapping = + from client in buildClient(settings) + from fromContext in useContext(client, context => + from attributes in context.GetConnection().Bind(c => c.GetAttributes().ToAff(l => l)) + from structure in context.CallFunction("BAPI_USER_GET_DETAIL", f => + f.SetField("USERNAME", attributes.User), + f => f.MapStructure("address", s => s.ToDictionary())) + from userName in getValue(structure["FULLNAME"]) - var withFieldMapping = useContext(connectionEffect, context => - from attributes in context.GetConnection().Bind(c => c.GetAttributes().ToAff(l => l)) - from structure in context.CallFunction("BAPI_USER_GET_DETAIL", f => - f.SetField("USERNAME", attributes.User), - f => f.MapStructure("address", s=>s.ToDictionary())) - from userName in getValue(structure["FULLNAME"]) - - select userName); + select userName) + select fromContext; result = await withFieldMapping.Run(SAPRfcRuntime.Default); Console.WriteLine("call_getFullName_with_abapValue: " + result); return; - RfcError Callback(string command) + static RfcError Callback(string command) { _callbackCommand = command; diff --git a/test/YaNco.Core.Tests/ConnectionTests.cs b/test/YaNco.Core.Tests/ConnectionTests.cs index a2ba4798..70d38caf 100644 --- a/test/YaNco.Core.Tests/ConnectionTests.cs +++ b/test/YaNco.Core.Tests/ConnectionTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Dbosoft.YaNco; using Dbosoft.YaNco.Test; +using Dbosoft.YaNco.Traits; using LanguageExt; using Moq; using Xunit; diff --git a/test/YaNco.Core.Tests/RfcContextTests.cs b/test/YaNco.Core.Tests/RfcContextTests.cs index 7b1adafa..26013c2a 100644 --- a/test/YaNco.Core.Tests/RfcContextTests.cs +++ b/test/YaNco.Core.Tests/RfcContextTests.cs @@ -3,6 +3,7 @@ using System.Threading.Tasks; using Dbosoft.YaNco; using Dbosoft.YaNco.Live; +using Dbosoft.YaNco.Traits; using Dbosoft.YaNco.TypeMapping; using LanguageExt; using Moq; @@ -99,7 +100,7 @@ public async Task Can_access_buildIn_SAPRfcRuntime() { await rfcContext.GetConnection().IfLeft(l => l.Throw()); - var actFieldMapper = await rfcContext.RunIO( () => + var actFieldMapper = await rfcContext.RunIO( _ => from rt in Prelude.runtime() select rt.Env.Settings.FieldMapper ) diff --git a/test/YaNco.Core.Tests/RfcMock/IOMockExtensions.cs b/test/YaNco.Core.Tests/RfcMock/IOMockExtensions.cs index ae55876f..8f42090d 100644 --- a/test/YaNco.Core.Tests/RfcMock/IOMockExtensions.cs +++ b/test/YaNco.Core.Tests/RfcMock/IOMockExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using Dbosoft.YaNco; +using Dbosoft.YaNco.Traits; using LanguageExt; using Moq;