diff --git a/.gitignore b/.gitignore index de685d7..e4a6004 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,10 @@ # RFC traces *.trc +*.log # sapnwrfc.ini example -gorfc/sapnwrfc.ini +# gorfc/sapnwrfc.ini # Packages GoRFC/pkg/ @@ -44,3 +45,7 @@ Thumbs.db *.iml .idea +### VSCode +.vscode +*.code-workspace + diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..da71c84 --- /dev/null +++ b/CHANGES @@ -0,0 +1,17 @@ +CHANGES +======= + +0.0.1 (2016-02-01) +------------------ +- Initial release + +0.1.0 (2020-05-04) +------------------ +- Darwin support +- New connection parameters added, type changed to map +- New connection attributes added, type changed to map +- Table parameter accepts also array of variables +- ABAP datatypes conversions fixed: + - Bytes: RFCTYPE_BYTE, RFCTYPE_XSTRING + - BCD: RFCTYPE_BCD +- ABAP INT8 type added: RFCTYPE_INT8 \ No newline at end of file diff --git a/README.md b/README.md index 9fd9c5a..39441f9 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,66 @@ -# SAP NW RFC Connector for [Go](https://golang.org) +[SAP NetWeawer RFC SDK](https://support.sap.com/en/product/connectors/nwrfcsdk.html) client bindings for GO. -[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/SAP/gorfc/gorfc) -[![Go Report Card](https://goreportcard.com/badge/github.com/SAP/gorfc)](https://goreportcard.com/report/github.com/SAP/gorfc) -[![license](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/SAP/gorfc/blob/master/LICENSE) +[![license](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/SAP/gorfc/blob/master/LICENSE) +[![Go Report Card](https://goreportcard.com/badge/github.com/SAP/gorfc)](https://goreportcard.com/report/github.com/SAP/gorfc) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/SAP/gorfc/gorfc) -The *gorfc* package provides bindings for *SAP NW RFC Library*, for a comfortable way of calling remote enabled ABAP function modules (RFMs) from [Go](https://golang.org). +## Features -The current release is fully functional on Linux and experimental on Windows and macOS see the [Issue #1](https://github.com/SAP/gorfc/issues/1). +- Stateless and stateful connections (multiple function calls in the same ABAP session / same context) +- Automatic conversion between GO and ABAP datatypes -## Table of contents +## Supported Platforms -* [Platforms and Prerequisites](#platforms-and-prerequisites) -* [Install](#install) - * [GO](#install-and-configure-go) - * [SAP NW RFC Library](#install-sap-nw-rfc-library) - * [gorfc](#install-gorfc) -* [Getting Started](#getting-started) -* [To Do](#to-do) -* [References](#references) +- Windows 10, macOS, Linux -## Platforms and Prerequisites +## Prerequisites -The SAP NW RFC Library is a prerequisite for using the Go RFC connector and must be installed on the same system. It is available on many platforms supported by Go, except Plan 9 and BSD. +### All platforms -A prerequisite to download *SAP NW RFC Library* is having a **customer or partner account** on *SAP Service Marketplace* . If you are SAP employee please check SAP OSS note [1037575 - Software download authorizations for SAP employees](http://service.sap.com/sap/support/notes/1037575). +- GOLANG [requirements](https://golang.org/doc/install#requirements) -The _SAP NW RFC Library_ is fully backwards compatible, supporting all NetWeaver systems, from today, down to release R/3 4.0. You can and should always use the newest version released on Service Marketplace and connect to older systems as well. +- SAP NWRFC SDK 7.50 PL3 or later must be [downloaded](https://launchpad.support.sap.com/#/softwarecenter/template/products/_APP=00200682500000001943&_EVENT=DISPHIER&HEADER=Y&FUNCTIONBAR=N&EVENT=TREE&NE=NAVIGATE&ENR=01200314690100002214&V=MAINT) (SAP partner or customer account required) and [locally installed](http://sap.github.io/node-rfc/install.html#sap-nw-rfc-library-installation) + + - Using the latest version is recommended as SAP NWRFC SDK is fully backwards compatible, supporting all NetWeaver systems, from today S4, down to R/3 release 4.6C. + - SAP NWRFC SDK [overview](https://support.sap.com/en/product/connectors/nwrfcsdk.html) and [release notes](https://launchpad.support.sap.com/#/softwarecenter/object/0020000000340702020) + +- Build from source on macOS and older Linux systems, may require `uchar.h` file, attached to [SAP OSS Note 2573953](https://launchpad.support.sap.com/#/notes/2573953), to be copied to SAP NWRFC SDK include directory: [documentation](http://sap.github.io/PyRFC/install.html#macos) + +### Windows + +- [Visual C++ Redistributable Package for Visual Studio 2013](https://www.microsoft.com/en-US/download/details.aspx?id=40784) is required for runtime, see [SAP Note 2573790 - Installation, Support and Availability of the SAP NetWeaver RFC Library 7.50](https://launchpad.support.sap.com/#/notes/2573790) + +- Build toolchain requires GCC and [MinGW](http://mingw-w64.org). Using TDM_GCC may lead to issues: https://stackoverflow.com/questions/35004744/golang-oci8-error-adding-symbols-file-in-wrong-format + +### macOS + +- Build toolchain requires GCC and Xcode Command Line Tools: + +```shell +$ xcode-select --install +``` + +- The macOS firewall stealth mode must be disabled ([Can't ping a machine - why?](https://discussions.apple.com/thread/2554739)): + +```shell +sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode off +``` + +- Remote paths must be set in SAP NWRFC SDK for macOS: [documentation](http://sap.github.io/PyRFC/install.html#macos) + +- Build from source requires `uchar.h` file, attached to [SAP OSS Note 2573953](https://launchpad.support.sap.com/#/notes/2573953), to be copied to SAP NWRFC SDK include directory: [documentation](http://sap.github.io/PyRFC/install.html#macos) + +- Optionally: [valgrind](https://stackoverflow.com/questions/58360093/how-to-install-valgrind-on-macos-catalina-10-15-with-homebrew) + +## SPJ articles + +Highly recommended reading about RFC communication and SAP NW RFC Library, published in the SAP Professional Journal (SPJ) + +- [Part I RFC Client Programming](https://wiki.scn.sap.com/wiki/x/zz27Gg) + +- [Part II RFC Server Programming](https://wiki.scn.sap.com/wiki/x/9z27Gg) + +- [Part III Advanced Topics](https://wiki.scn.sap.com/wiki/x/FD67Gg) ## Install @@ -119,7 +154,7 @@ func abapSystem() gorfc.ConnectionParameter { Ashost: "11.111.11.111", Sysnr: "00", Saprouter: "/H/222.22.222.22/S/2222/W/xxxxx/H/222.22.222.222/H/", - } + } } func main() { @@ -141,7 +176,7 @@ func main() { "RFCDATA1": "HELLÖ SÄP", "RFCDATA2": "DATA222", }, - } + } r, _ := c.Call("STFC_STRUCTURE", params) assert.NotNil(t, r["ECHOSTRUCT"]) @@ -154,7 +189,7 @@ func main() { assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"]) assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"]) assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"]) - // assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"]) + // assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"]) assert.Equal(t, importStruct["RFCTIME"].(time.Time).Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("15. assert.Equal(t, importStruct["RFCDATE"].(time.Time).Format("20060102"), e/Users/d037732/Downloads/gorfc/README.mdchoStruct["RFCDATE"].(time.Time).Format(". assert.Equal(t, importStruct["RFCDATA1"], echoStruct["RFCDATA1"]) @@ -166,15 +201,10 @@ func main() { c.Close() ``` -## To Do - -* Improve the documentation -* Fix Windows compiler flags - ## References -* [GO Installation](https://golang.org/doc/install) -* [GO Configuration](https://golang.org/doc/code.html) -* [GO Environment Variables](https://golang.org/cmd/go/#hdr-Environment_variables) -* [GO on Windows Example](http://www.wadewegner.com/2014/12/easy-go-programming-setup-for-windows/) -* [Another GO on Windows Example](https://github.com/abourget/getting-started-with-golang/blob/master/Getting_Started_for_Windows.md) +- [GO Installation](https://golang.org/doc/install) +- [GO Configuration](https://golang.org/doc/code.html) +- [GO Environment Variables](https://golang.org/cmd/go/#hdr-Environment_variables) +- [GO on Windows Example](http://www.wadewegner.com/2014/12/easy-go-programming-setup-for-windows/) +- [Another GO on Windows Example](https://github.com/abourget/getting-started-with-golang/blob/master/Getting_Started_for_Windows.md) diff --git a/example/hello_gorfc.go b/example/hello_gorfc.go index f55967f..2118856 100644 --- a/example/hello_gorfc.go +++ b/example/hello_gorfc.go @@ -3,29 +3,32 @@ package main import ( "fmt" "reflect" - "testing" "time" "github.com/sap/gorfc/gorfc" - "github.com/stretchr/testify/assert" ) -func abapSystem() gorfc.ConnectionParameter { - return gorfc.ConnectionParameter{ - Dest: "I64", - Client: "800", - User: "demo", - Passwd: "welcome", - Lang: "EN", - Ashost: "10.117.24.158", - Sysnr: "00", - Saprouter: "/H/203.13.155.17/S/3299/W/xjkb3d/H/172.19.137.194/H/", +func abapSystem() gorfc.ConnectionParameters { + return gorfc.ConnectionParameters{ + "user": "demo", + "passwd": "welcome", + "ashost": "10.68.110.51", + "sysnr": "00", + "client": "620", + "lang": "EN", } } func main() { - c, _ := gorfc.ConnectionFromParams(abapSystem()) - var t *testing.T + c, err := gorfc.ConnectionFromParams(abapSystem()) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(c.Alive()) + + attrs, _ := c.GetConnectionAttributes() + fmt.Println("Connection attributes", attrs) params := map[string]interface{}{ "IMPORTSTRUCT": map[string]interface{}{ @@ -39,30 +42,22 @@ func main() { "RFCHEX3": []byte{255, 254, 253}, "RFCTIME": time.Now(), "RFCDATE": time.Now(), - "RFCDATA1": "Hellö SÄP", + "RFCDATA1": "HELLÖ SÄP", "RFCDATA2": "DATA222", }, } r, _ := c.Call("STFC_STRUCTURE", params) - assert.NotNil(t, r["ECHOSTRUCT"]) + fmt.Println(r["ECHOSTRUCT"]) importStruct := params["IMPORTSTRUCT"].(map[string]interface{}) echoStruct := r["ECHOSTRUCT"].(map[string]interface{}) - assert.Equal(t, importStruct["RFCFLOAT"], echoStruct["RFCFLOAT"]) - assert.Equal(t, importStruct["RFCCHAR1"], echoStruct["RFCCHAR1"]) - assert.Equal(t, importStruct["RFCCHAR2"], echoStruct["RFCCHAR2"]) - assert.Equal(t, importStruct["RFCCHAR4"], echoStruct["RFCCHAR4"]) - assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"]) - assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"]) - assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"]) - // assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"]) - assert.Equal(t, importStruct["RFCTIME"].(time.Time).Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("150405")) - assert.Equal(t, importStruct["RFCDATE"].(time.Time).Format("20060102"), echoStruct["RFCDATE"].(time.Time).Format("20060102")) - assert.Equal(t, importStruct["RFCDATA1"], echoStruct["RFCDATA1"]) - assert.Equal(t, importStruct["RFCDATA2"], echoStruct["RFCDATA2"]) - - fmt.Println(reflect.TypeOf(importStruct["RFCDATE"])) - fmt.Println(reflect.TypeOf(importStruct["RFCTIME"])) + fmt.Println(echoStruct) + // empty time + fmt.Println(importStruct["RFCDATE"], reflect.TypeOf(importStruct["RFCDATE"])) + fmt.Println(echoStruct["RFCDATE"], reflect.TypeOf(echoStruct["RFCDATE"])) + // empty date + fmt.Println(importStruct["RFCTIME"], reflect.TypeOf(importStruct["RFCTIME"])) + fmt.Println(echoStruct["RFCTIME"], reflect.TypeOf(echoStruct["RFCTIME"])) c.Close() } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..24151f3 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/sap/gorfc + +go 1.14 + +require github.com/stretchr/testify v1.5.1 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c0565d7 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/gorfc/.editorconfig b/gorfc/.editorconfig new file mode 100644 index 0000000..7b8d0aa --- /dev/null +++ b/gorfc/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = false \ No newline at end of file diff --git a/gorfc/gorfc.go b/gorfc/gorfc.go index edb5101..69f1eb1 100644 --- a/gorfc/gorfc.go +++ b/gorfc/gorfc.go @@ -1,60 +1,61 @@ -// +build linux,cgo amd64,cgo +// +build linux,cgo amd64,cgo darwin,cgo -// gorfc wraps the SAP NetWeaver RFC library written in C. -// Its provides methods for maintaining a connection to an ABAP backend and calling remote enabled functions from Go. -// The functions of the library take and return Go data types. +// Package gorfc provides SAP NetWeawer RFC SDK client bindings for GO package gorfc /* +#cgo windows CFLAGS: -DSAPonNT -D_CRT_NON_CONFORMING_SWPRINTFS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE -D_CONSOLE +#cgo windows CFLAGS: -D_AFXDLL -DWIN32 -D_WIN32_WINNT=0x0502 -DWIN64 -D_AMD64_ -DNDEBUG -DSAPwithUNICODE -DUNICODE -D_UNICODE +#cgo windows CFLAGS: -DSAPwithTHREADS -D_ATL_ALLOW_CHAR_UNSIGNED -DSAP_PLATFORM_MAKENAME=ntintel +#cgo windows CFLAGS: -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D__NO_MATH_INLINES +#cgo windows CFLAGS: -O2 -g -pipe -m64 -mwindows -pthread -march=x86-64 +#cgo windows CFLAGS: -fno-strict-aliasing -fno-omit-frame-pointer -fexceptions -funsigned-char +#cgo windows CFLAGS: -Wall -Wno-uninitialized -Wno-long-long +#cgo windows CFLAGS: -Wcast-align -Wunused-variable +// todo -EHs ? +// todo -Gy ? -ffunction-sections -fdata-sections +// todo MD ? -lpthread -lm +// todo -nologo -W3 -Z7 -GL -O2 -Oy- /we4552 /we4700 /we4789 + +#cgo windows CFLAGS: -IC:/Tools/nwrfcsdk/include/ +#cgo windows LDFLAGS: -LC:/Tools/nwrfcsdk/lib/ -lsapnwrfc -llibsapucum + +#cgo windows LDFLAGS: -O2 -g -pthread -pie -fPIE +#cgo windows LDFLAGS: -OPT:REF -LTCG +// todo -NXCOMPAT -STACK:0x2000000 -SWAPRUN:NET -DEBUG -DEBUGTYPE:CV,FIXUP -MACHINE:amd64 -nologo + #cgo linux CFLAGS: -DNDEBUG -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DSAPonUNIX #cgo linux CFLAGS: -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -DSAPonLIN -#cgo linux CFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer +#cgo linux CFLAGS: -O2 -g -fno-strict-aliasing -fno-omit-frame-pointer #cgo linux CFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long #cgo linux CFLAGS: -Wcast-align -pthread -pipe -Wno-unused-variable #cgo linux CFLAGS: -I/usr/local/sap/nwrfcsdk/include #cgo linux LDFLAGS: -L/usr/local/sap/nwrfcsdk/lib -lsapnwrfc -lsapucum -#cgo linux LDFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer -#cgo linux LDFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long -#cgo linux LDFLAGS: -Wcast-align -pthread - -#cgo windows CFLAGS: -DNDEBUG -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DSAPonWIN -#cgo windows CFLAGS: -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -#cgo windows CFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer -#cgo windows CFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long -#cgo windows CFLAGS: -Wcast-align -pipe -Wunused-variable +#cgo linux LDFLAGS: -O2 -g -pthread -#cgo windows CFLAGS: -IC:/nwrfcsdk/include/ -#cgo windows LDFLAGS: -LC:/nwrfcsdk/lib/ -lsapnwrfc -llibsapucum +#cgo darwin CFLAGS: -Wall -O2 -Wno-uninitialized -Wcast-align +#cgo darwin CFLAGS: -DSAP_UC_is_wchar -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -DSAPonLIN +#cgo darwin CFLAGS: -fexceptions -funsigned-char -fno-strict-aliasing -fPIC -pthread -std=c17 -mmacosx-version-min=10.15 +#cgo darwin CFLAGS: -fno-omit-frame-pointer -#cgo windows LDFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer -#cgo windows LDFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long -#cgo windows LDFLAGS: -Wcast-align +#cgo darwin CFLAGS: -I/usr/local/sap/nwrfcsdk/include +#cgo darwin LDFLAGS: -L/usr/local/sap/nwrfcsdk/lib -lsapnwrfc -lsapucum +#cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/sap/nwrfcsdk/lib -#cgo darwin LDFLAGS: -lsapnwrfc -lsapucum -#cgo darwin LDFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer -#cgo darwin LDFLAGS: -m64 -fexceptions -funsigned-char -Wall -Wno-uninitialized -Wno-long-long -#cgo darwin LDFLAGS: -Wcast-align -pthread - -#cgo darwin CFLAGS: -DNDEBUG -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -DSAPonUNIX -#cgo darwin CFLAGS: -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -DSAPonDARW -#cgo darwin CFLAGS: -O2 -minline-all-stringops -g -fno-strict-aliasing -fno-omit-frame-pointer -#cgo darwin CFLAGS: -m64 -fexceptions -Wall -Wno-uninitialized -Wno-long-long -#cgo darwin CFLAGS: -Wcast-align -pthread -pipe -Wno-unused-variable +#cgo darwin LDFLAGS: -O2 -g -pthread +#cgo darwin LDFLAGS: -stdlib=libc++ +#cgo darwin LDFLAGS: -mmacosx-version-min=10.15 #include static SAP_UC* GoMallocU(unsigned size) { - return mallocU(size); + return (SAP_UC*)(mallocU(size)); } -//static SAP_UC* GoMemsetU(SAP_UTF16 * s, int c, size_t n) { -// return memsetU(s, c, n); -//} - -static int GoStrlenU(SAP_UTF16 *str) { +static unsigned GoStrlenU(SAP_UTF16 *str) { return strlenU(str); } @@ -70,10 +71,29 @@ import ( "unsafe" ) +/* +static uint GoStrlenU(SAP_UTF16 *str) { + return strlenU(str); +} + +static uint GoStrlenU16(SAP_UTF16 *str) { + return strlenU16(str); +} + +static SAP_UC* GoMemsetU(SAP_UTF16 * s, int c, size_t n) { + return (SAP_UC *)memsetU(s, c, n); +} + +static uint GoStrlenU(SAP_UTF16 *str) { + return strlenU(str); +} +*/ + //################################################################################ //# RFC ERROR # //################################################################################ +// RfcError is returned by SAP NWRFC SDK type RfcError struct { Description string ErrorInfo rfcSDKError @@ -96,28 +116,19 @@ func rfcError(errorInfo C.RFC_ERROR_INFO, format string, a ...interface{}) *RfcE func fillString(gostr string) (sapuc *C.SAP_UC, err error) { var rc C.RFC_RC var errorInfo C.RFC_ERROR_INFO - var result_len C.uint - sapuc_size := C.uint(len(gostr) + 1) - sapuc = C.GoMallocU(sapuc_size) - cStr := (*C.uchar)(unsafe.Pointer(C.CString(gostr))) - defer C.free(unsafe.Pointer(cStr)) + var resultLen C.uint + sapucSize := C.uint(len(gostr) + 1) + sapuc = C.GoMallocU(sapucSize) *sapuc = 0 - - rc = C.RfcUTF8ToSAPUC((*C.RFC_BYTE)(cStr), C.uint(len(gostr)), sapuc, &sapuc_size, &result_len, &errorInfo) + cStr := C.CString(gostr) + defer C.free(unsafe.Pointer(cStr)) + rc = C.RfcUTF8ToSAPUC((*C.RFC_BYTE)(cStr), C.uint(len(gostr)), sapuc, &sapucSize, &resultLen, &errorInfo) if rc != C.RFC_OK { err = rfcError(errorInfo, "Could not fill the string \"%v\"", gostr) } return } -// fillByte allocates memory for the return value that has to be freed -func fillBytes(gobytes []byte) (bytes *C.SAP_RAW) { - size := C.size_t(len(gobytes)) - bytes = (*C.SAP_RAW)(C.malloc(size)) - C.memcpy(unsafe.Pointer(bytes), unsafe.Pointer(&gobytes[0]), size) - return -} - func fillFunctionParameter(funcDesc C.RFC_FUNCTION_DESC_HANDLE, container C.RFC_FUNCTION_HANDLE, goName string, value interface{}) (err error) { var rc C.RFC_RC var errorInfo C.RFC_ERROR_INFO @@ -161,29 +172,40 @@ func fillVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP return rfcError(errorInfo, "Could not get table") } err = fillTable(typeDesc, table, value) - case C.RFCTYPE_CHAR: - cValue, err = fillString(reflect.ValueOf(value).String()) - rc = C.RfcSetChars(container, cName, (*C.RFC_CHAR)(cValue), C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))), &errorInfo) case C.RFCTYPE_BYTE: - bValue = fillBytes(reflect.ValueOf(value).Bytes()) - rc = C.RfcSetBytes(container, cName, bValue, C.uint(len(reflect.ValueOf(value).Bytes())), &errorInfo) + bValue = (*C.SAP_RAW)(C.CBytes(reflect.ValueOf(value).Bytes())) + cLen := C.uint(len(reflect.ValueOf(value).Bytes())) + rc = C.RfcSetBytes(container, cName, bValue, cLen, &errorInfo) case C.RFCTYPE_XSTRING: - bValue = fillBytes(reflect.ValueOf(value).Bytes()) - rc = C.RfcSetXString(container, cName, bValue, C.uint(len(reflect.ValueOf(value).Bytes())), &errorInfo) + bValue = (*C.SAP_RAW)(C.CBytes(reflect.ValueOf(value).Bytes())) + cLen := C.uint(len(reflect.ValueOf(value).Bytes())) + rc = C.RfcSetXString(container, cName, bValue, cLen, &errorInfo) + case C.RFCTYPE_CHAR: + cValue, err = fillString(reflect.ValueOf(value).String()) + //cLen := (C.uint)(len(reflect.ValueOf(value).String())) + cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) + rc = C.RfcSetChars(container, cName, (*C.RFC_CHAR)(cValue), cLen, &errorInfo) case C.RFCTYPE_STRING: cValue, err = fillString(reflect.ValueOf(value).String()) - rc = C.RfcSetString(container, cName, cValue, C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))), &errorInfo) + //cLen := (C.uint)(len(reflect.ValueOf(value).String())) + cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) + rc = C.RfcSetString(container, cName, cValue, cLen, &errorInfo) case C.RFCTYPE_NUM: cValue, err = fillString(reflect.ValueOf(value).String()) - rc = C.RfcSetNum(container, cName, (*C.RFC_NUM)(cValue), C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))), &errorInfo) + //cLen := (C.uint)(len(reflect.ValueOf(value).String())) + cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) + rc = C.RfcSetNum(container, cName, (*C.RFC_NUM)(cValue), cLen, &errorInfo) case C.RFCTYPE_BCD: - // support for float missing cValue, err = fillString(reflect.ValueOf(value).String()) - rc = C.RfcSetString(container, cName, cValue, C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))), &errorInfo) + //cLen := (C.uint)(len(reflect.ValueOf(value).String())) + cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) + rc = C.RfcSetString(container, cName, cValue, cLen, &errorInfo) case C.RFCTYPE_FLOAT: rc = C.RfcSetFloat(container, cName, C.RFC_FLOAT(reflect.ValueOf(value).Float()), &errorInfo) case C.RFCTYPE_INT, C.RFCTYPE_INT1, C.RFCTYPE_INT2: rc = C.RfcSetInt(container, cName, C.RFC_INT(reflect.ValueOf(value).Int()), &errorInfo) + case C.RFCTYPE_INT8: + rc = C.RfcSetInt8(container, cName, C.RFC_INT8(reflect.ValueOf(value).Int()), &errorInfo) case C.RFCTYPE_DATE: cValue, err = fillString(value.(time.Time).Format("20060102")) rc = C.RfcSetDate(container, cName, (*C.RFC_CHAR)(cValue), &errorInfo) @@ -208,13 +230,13 @@ func fillStructure(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_STRUCTURE_HA s := reflect.ValueOf(value) if s.Type().Kind() == reflect.Map { + // Table passed as array of maps keys := s.MapKeys() if len(keys) > 0 { if keys[0].Kind() == reflect.String { for _, nameValue := range keys { fieldName := nameValue.String() fieldValue := s.MapIndex(nameValue).Interface() - err = fillStructureField(typeDesc, container, fieldName, fieldValue) } } else { @@ -222,14 +244,15 @@ func fillStructure(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_STRUCTURE_HA } } } else if s.Type().Kind() == reflect.Struct { + // Table passed as array of structures for i := 0; i < s.NumField(); i++ { fieldName := s.Type().Field(i).Name fieldValue := s.Field(i).Interface() - err = fillStructureField(typeDesc, container, fieldName, fieldValue) } } else { - return rfcError(errorInfo, "Structures can only be passed as types map[string]interface{} or go-structures") + // Table passed as array of variables + err = fillStructureField(typeDesc, container, "", s.Interface()) } return } @@ -258,7 +281,6 @@ func fillTable(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_TABLE_HANDLE, li if lineHandle == nil { return rfcError(errorInfo, "Could not append new row to table") } - err = fillStructure(typeDesc, lineHandle, line.Interface()) } return @@ -269,35 +291,32 @@ func fillTable(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_TABLE_HANDLE, li //################################################################################ //# Wrapper functions take C values and return Go values -func wrapString(uc *C.SAP_UC, strip bool) (result string, err error) { - return nWrapString(uc, -1, strip) +func wrapString(sapuc *C.SAP_UC, strip bool) (string, error) { + return nWrapString(sapuc, C.uint(C.strlenU((*C.ushort)(sapuc))), strip) } -func nWrapString(uc *C.SAP_UC, length C.int, strip bool) (result string, err error) { - var rc C.RFC_RC +func nWrapString(sapuc *C.SAP_UC, sapucLength C.uint, strip bool) (string, error) { var errorInfo C.RFC_ERROR_INFO - if length == -1 { - length = C.int(C.GoStrlenU((*C.SAP_UTF16)(uc))) - } - if length == 0 { - return "", err + var rc C.RFC_RC + var resultLength C.uint + + if sapucLength == 0 { + return "", nil } - utf8Size := C.uint(length*3) + 1 - utf8str := (*C.char)(unsafe.Pointer(C.malloc((C.size_t)(utf8Size)))) - defer C.free(unsafe.Pointer(utf8str)) // _todo: Memory access error on Windows only, when trying to free RFCCHAR1 of RFCTABLE in function call test - *utf8str = 0 - resultLen := C.uint(0) - rc = C.RfcSAPUCToUTF8(uc, (C.uint)(length), (*C.RFC_BYTE)(unsafe.Pointer(utf8str)), &utf8Size, &resultLen, &errorInfo) + utf8size := C.uint(3*sapucLength + 1) + utf8Str := (*C.RFC_BYTE)(C.malloc((C.size_t)(utf8size))) + defer C.free(unsafe.Pointer(utf8Str)) + + rc = C.RfcSAPUCToUTF8(sapuc, C.uint(sapucLength), utf8Str, &utf8size, &resultLength, &errorInfo) if rc != C.RFC_OK { - return result, rfcError(errorInfo, "Failed wrapping a C string") + return "", fmt.Errorf("wrapString sapucLength %v utf8size %v", sapucLength, utf8size) } - result = C.GoString(utf8str) + result := C.GoString((*C.char)(unsafe.Pointer(utf8Str))) if strip { result = strings.Trim(result, "\x00 ") - return } - return + return result, nil } type rfcSDKError struct { @@ -332,40 +351,12 @@ func (err rfcSDKError) String() string { return fmt.Sprintf("rfcSDKError[%v, %v, %v, %v, %v, %v, %v, %v, %v, %v]", err.Message, err.Code, err.Key, err.AbapMsgClass, err.AbapMsgType, err.AbapMsgNumber, err.AbapMsgV1, err.AbapMsgV2, err.AbapMsgV3, err.AbapMsgV4) } -type ConnectionAttributes struct { - Dest string // RFC destination - Host string // Own host name - PartnerHost string // Partner host name - SysNumber string // R/3 system number - SysId string // R/3 system ID - Client string // Client ("Mandant") - User string // User - Language string // Language - Trace string // Trace level (0-3) - IsoLanguage string // 2-byte ISO-Language - Codepage string // Own code page - PartnerCodepage string // Partner code page - RfcRole string // C/S: RFC Client / RFC Server - Type string // 2/3/E/R: R/2,R/3,Ext,Reg.Ext - PartnerType string // 2/3/E/R: R/2,R/3,Ext,Reg.Ext - Rel string // My system release - PartnerRel string // Partner system release - KernelRel string // Partner kernel release - CpicConvId string // CPI-C Conversation ID - ProgName string // Name of the calling APAB program (report, module pool) - PartnerBytesPerChar string // Number of bytes per character in the backend's current codepage. Note this is different from the semantics of the PCS parameter. - PartnerSystemCodepage string // Partner system code page - Reserved string // Reserved for later use -} - -func (connAttr ConnectionAttributes) String() string { - return fmt.Sprintf("ConnectionAttributes:\n dest= %v\n host= %v\n partnerHost= %v\n sysNumber= %v\n sysID= %v\n client= %v\n user= %v\n lang= %v\n trace= %v\n isoLang= %v\n codePage= %v\n partnerCodepage= %v\n RFCRole= %v\n partnerType= %v\n rel= %v\n partnerRel= %v\n kernalRel= %v\n CPI-CConvId= %v\n progName= %v\n partnerBytesPerChar= %v\n partnerSystemCodepage= %v\n reserved= %v", - connAttr.Dest, connAttr.Host, connAttr.PartnerHost, connAttr.SysNumber, connAttr.SysId, connAttr.Client, connAttr.User, connAttr.Language, - connAttr.Trace, connAttr.IsoLanguage, connAttr.Codepage, connAttr.PartnerCodepage, connAttr.RfcRole, connAttr.PartnerType, connAttr.Rel, - connAttr.PartnerRel, connAttr.KernelRel, connAttr.CpicConvId, connAttr.ProgName, connAttr.PartnerBytesPerChar, connAttr.PartnerSystemCodepage, connAttr.Reserved) -} +// ConnectionAttributes returned by getConnectionInfo() method +type ConnectionAttributes map[string]string func wrapConnectionAttributes(attributes C.RFC_ATTRIBUTES, strip bool) (connAttr ConnectionAttributes, err error) { + connAttr = make(map[string]string) + dest, err := nWrapString(&attributes.dest[0], 64, strip) host, err := nWrapString(&attributes.host[0], 100, strip) partnerHost, err := nWrapString(&attributes.partnerHost[0], 100, strip) @@ -388,10 +379,35 @@ func wrapConnectionAttributes(attributes C.RFC_ATTRIBUTES, strip bool) (connAttr progName, err := nWrapString(&attributes.progName[0], 128, strip) partnerBytesPerChar, err := nWrapString(&attributes.partnerBytesPerChar[0], 1, strip) partnerSystemCodepage, err := nWrapString(&attributes.partnerSystemCodepage[0], 4, strip) - reserved, err := nWrapString(&attributes.reserved[0], 78, strip) + partnerIP, err := nWrapString(&attributes.partnerIP[0], 15, strip) + partnerIPv6, err := nWrapString(&attributes.partnerIPv6[0], 45, strip) + //reserved, err := nWrapString(&attributes.reserved[0], 17, strip) + + connAttr["dest"] = dest + connAttr["host"] = host + connAttr["partnerHost"] = partnerHost + connAttr["sysNumber"] = sysNumber + connAttr["sysId"] = sysId + connAttr["client"] = client + connAttr["user"] = user + connAttr["language"] = language + connAttr["trace"] = trace + connAttr["isoLanguage"] = isoLanguage + connAttr["codepage"] = codepage + connAttr["partnerCodepage"] = partnerCodepage + connAttr["rfcRole"] = rfcRole + connAttr["type"] = _type + connAttr["partnerType"] = partnerType + connAttr["rel"] = rel + connAttr["partnerRel"] = partnerRel + connAttr["kernelRel"] = kernelRel + connAttr["cpicConvId"] = cpicConvId + connAttr["progName"] = progName + connAttr["partnerBytesPerChar"] = partnerBytesPerChar + connAttr["partnerSystemCodepage"] = partnerSystemCodepage + connAttr["partnerIP"] = partnerIP + connAttr["partnerIPv6"] = partnerIPv6 - connAttr = ConnectionAttributes{dest, host, partnerHost, sysNumber, sysId, client, user, language, trace, isoLanguage, codepage, partnerCodepage, rfcRole, - _type, partnerType, rel, partnerRel, kernelRel, cpicConvId, progName, partnerBytesPerChar, partnerSystemCodepage, reserved} return } @@ -601,6 +617,7 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP var intValue C.RFC_INT var int1Value C.RFC_INT1 var int2Value C.RFC_INT2 + var int8Value C.RFC_INT8 var dateValue *C.RFC_CHAR var timeValue *C.RFC_CHAR @@ -627,7 +644,7 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP if rc != C.RFC_OK { return result, rfcError(errorInfo, "Failed getting chars") } - return nWrapString((*C.SAP_UC)(charValue), C.int(cLen), strip) + return nWrapString((*C.SAP_UC)(charValue), cLen, strip) case C.RFCTYPE_STRING: rc = C.RfcGetStringLength(container, cName, &strLen, &errorInfo) if rc != C.RFC_OK { @@ -650,31 +667,28 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP if rc != C.RFC_OK { return result, rfcError(errorInfo, "Failed getting num") } - return nWrapString((*C.SAP_UC)(numValue), C.int(cLen), strip) + return nWrapString((*C.SAP_UC)(numValue), cLen, strip) case C.RFCTYPE_BYTE: byteValue = (*C.SAP_RAW)(C.malloc(C.size_t(cLen))) defer C.free(unsafe.Pointer(byteValue)) - rc = C.RfcGetBytes(container, cName, byteValue, cLen, &errorInfo) if rc != C.RFC_OK { return result, rfcError(errorInfo, "Failed getting bytes") } - return (*[1 << 30]byte)(unsafe.Pointer(byteValue))[:cLen:cLen], err + return C.GoBytes(unsafe.Pointer(byteValue), C.int(cLen)), err case C.RFCTYPE_XSTRING: rc = C.RfcGetStringLength(container, cName, &strLen, &errorInfo) if rc != C.RFC_OK { return result, rfcError(errorInfo, "Failed getting xstring length") } - byteValue = (*C.SAP_RAW)(C.malloc(C.size_t(strLen + 1))) + byteValue = (*C.SAP_RAW)(C.malloc(C.size_t(strLen))) defer C.free(unsafe.Pointer(byteValue)) - *byteValue = 0 - rc = C.RfcGetXString(container, cName, byteValue, strLen, &resultLen, &errorInfo) if rc != C.RFC_OK { return result, rfcError(errorInfo, "Failed getting xstring") } - return (*[1 << 30]byte)(unsafe.Pointer(byteValue))[:resultLen:resultLen], err + return C.GoBytes(unsafe.Pointer(byteValue), C.int(cLen)), err case C.RFCTYPE_BCD: // An upper bound for the length of the _string representation_ // of the BCD is given by (2*cLen)-1 (each digit is encoded in 4bit, @@ -684,20 +698,18 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP strLen = 2*cLen + 1 stringValue = C.GoMallocU(strLen + 1) defer C.free(unsafe.Pointer(stringValue)) - rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo) - /*if rc == 23: # Buffer too small, use returned requried result length - print("Warning: Buffer for BCD (cLen={}, buffer={}) too small: " - "trying with {}".format(cLen, strLen, resultLen)) - free(stringValue) - strLen = resultLen - stringValue = mallocU(strLen+1) - rc = RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo)*/ - if rc != C.RFC_OK { - return result, rfcError(errorInfo, "Failed getting BCD") + if rc == 23 { + //Buffer too small, use returned requried result length + C.free(unsafe.Pointer(stringValue)) + strLen = resultLen + stringValue = C.GoMallocU(strLen + 1) + rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo) + if rc != C.RFC_OK { + return result, rfcError(errorInfo, "Failed getting BCD") + } } return wrapString(stringValue, strip) - //return Decimal(wrapString(stringValue)) case C.RFCTYPE_FLOAT: rc = C.RfcGetFloat(container, cName, &floatValue, &errorInfo) if rc != C.RFC_OK { @@ -722,6 +734,12 @@ func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP return result, rfcError(errorInfo, "Failed getting int2") } return int(int2Value), err + case C.RFCTYPE_INT8: + rc = C.RfcGetInt8(container, cName, &int8Value, &errorInfo) + if rc != C.RFC_OK { + return result, rfcError(errorInfo, "Failed getting int8") + } + return int(int8Value), err case C.RFCTYPE_DATE: dateValue = (*C.RFC_CHAR)(C.malloc(8)) defer C.free(unsafe.Pointer(dateValue)) @@ -859,27 +877,7 @@ func GetNWRFCLibVersion() (major, minor, patchlevel uint) { //# CONNECTION # //################################################################################ -// ConnectionParameter holds all the connection parameters possible (at the moment). -type ConnectionParameter struct { - Dest string - Client string - User string // Username - Passwd string // Password - Lang string // Language - Trace string - Ashost string - Sysnr string - Mshost string - Msserv string - Sysid string - Group string - Snc_qop string - Snc_myname string - Snc_partnername string - Snc_lib string - Mysapsso2 string - Saprouter string -} +type ConnectionParameters map[string]string type Connection struct { handle C.RFC_CONNECTION_HANDLE @@ -887,7 +885,8 @@ type Connection struct { returnImportParams bool alive bool paramCount C.uint - connectionParams []C.RFC_CONNECTION_PARAMETER + connParams []C.RFC_CONNECTION_PARAMETER + connectionParams ConnectionParameters // tHandle C.RFC_TRANSACTION_HANDLE // active_transaction bool // uHandle C.RFC_UNIT_HANDLE @@ -895,40 +894,47 @@ type Connection struct { } func connectionFinalizer(conn *Connection) { - for _, connParam := range conn.connectionParams { + for _, connParam := range conn.connParams { C.free(unsafe.Pointer(connParam.name)) C.free(unsafe.Pointer(connParam.value)) } } -// ConnectionFromParams creates a new connection with the given connection parameters and tries to open it. If this is successfull it returns the connection else it returns nil. -func ConnectionFromParams(connParams ConnectionParameter) (conn *Connection, err error) { +// ConnectionFromParams creates a new connection with the given connection parameters and tries to open it. +// Returns the connection if successfull, otherwise nil. +func ConnectionFromParams(connectionParams ConnectionParameters) (conn *Connection, err error) { conn = new(Connection) - runtime.SetFinalizer(conn, connectionFinalizer) - p := reflect.ValueOf(&connParams).Elem() + conn.handle = nil - conn.paramCount = C.uint(p.NumField()) - conn.connectionParams = make([]C.RFC_CONNECTION_PARAMETER, conn.paramCount, conn.paramCount) conn.rstrip = true conn.returnImportParams = false conn.alive = false - for i := 0; i < p.NumField(); i++ { - conn.connectionParams[i].name, err = fillString(p.Type().Field(i).Name) - conn.connectionParams[i].value, err = fillString(p.Field(i).String()) + + runtime.SetFinalizer(conn, connectionFinalizer) + conn.paramCount = C.uint(len(connectionParams)) + conn.connectionParams = connectionParams + conn.connParams = make([]C.RFC_CONNECTION_PARAMETER, conn.paramCount, conn.paramCount) + i := 0 + for name, value := range conn.connectionParams { + conn.connParams[i].name, err = fillString(name) + conn.connParams[i].value, err = fillString(value) + i++ } if err != nil { return nil, err } + err = conn.Open() if err != nil { return nil, err } + return } // ConnectionFromDest creates a new connection with just the dest system id. func ConnectionFromDest(dest string) (conn *Connection, err error) { - return ConnectionFromParams(ConnectionParameter{Dest: dest}) + return ConnectionFromParams(ConnectionParameters{"dest": dest}) } // RStrip sets rstrip of the given connection to the passed parameter and returns the connection @@ -966,8 +972,8 @@ func (conn *Connection) Close() (err error) { // Open opens the connection and sets alive to true. func (conn *Connection) Open() (err error) { var errorInfo C.RFC_ERROR_INFO - conn.handle = C.RfcOpenConnection(&conn.connectionParams[0], conn.paramCount, &errorInfo) - if conn.handle == nil { + conn.handle = C.RfcOpenConnection(&conn.connParams[0], conn.paramCount, &errorInfo) + if errorInfo.code != C.RFC_OK { return rfcError(errorInfo, "Connection could not be opened") } else { conn.alive = true @@ -1005,15 +1011,10 @@ func (conn *Connection) Ping() (err error) { func (conn *Connection) GetConnectionAttributes() (connAttr ConnectionAttributes, err error) { var errorInfo C.RFC_ERROR_INFO var attributes C.RFC_ATTRIBUTES - if !conn.alive { - err = conn.Open() - if err != nil { - return - } - } + rc := C.RfcGetConnectionAttributes(conn.handle, &attributes, &errorInfo) - if rc != C.RFC_OK { - return connAttr, rfcError(errorInfo, "Could not get connection attributes") + if rc != C.RFC_OK || errorInfo.code != C.RFC_OK { + return nil, rfcError(errorInfo, "Could not get connection attributes") } return wrapConnectionAttributes(attributes, conn.rstrip) } @@ -1105,6 +1106,7 @@ func (conn *Connection) Call(goFuncName string, params interface{}) (result map[ } rc := C.RfcInvoke(conn.handle, funcCont, &errorInfo) + if rc != C.RFC_OK { return result, rfcError(errorInfo, "Could not invoke function \"%v\"", goFuncName) } diff --git a/gorfc/gorfc_test.go b/gorfc/gorfc_test.go index fff798d..99f8075 100644 --- a/gorfc/gorfc_test.go +++ b/gorfc/gorfc_test.go @@ -2,7 +2,10 @@ package gorfc import ( "fmt" + "os" "reflect" + + //"reflect" "strings" "testing" "time" @@ -10,25 +13,14 @@ import ( "github.com/stretchr/testify/assert" ) -// -// Helper Functions -// - -func isValueInList(value string, list []string) bool { - for _, v := range list { - if v == value { - return true - } - } - return false -} - // // NW RFC Lib Version // func TestNWRFCLibVersion(t *testing.T) { major, minor, patchlevel := GetNWRFCLibVersion() - assert.Equal(t, "7500.0.5", fmt.Sprintf("%d.%d.%d", major, minor, patchlevel)) // adapt to your NW RFC Lib version + assert.Equal(t, uint(7500), major) // adapt to your NW RFC Lib version + assert.Equal(t, uint(0), minor) + assert.Greater(t, patchlevel, uint(4)) } // @@ -42,100 +34,96 @@ func TestConnect(t *testing.T) { } assert.NotNil(t, c) assert.Nil(t, err) + assert.True(t, c.Alive()) c.Close() + assert.False(t, c.Alive()) } func TestConnectionAttributes(t *testing.T) { - fmt.Println("Connection test: Parameters") + fmt.Println("Connection test: Attributes") c, err := ConnectionFromParams(abapSystem()) - if err != nil { - t.SkipNow() - } + assert.Equal(t, err, nil) a, err := c.GetConnectionAttributes() - paramNames := []string{ - "Dest", - "Host", - "PartnerHost", - "SysNumber", - "SysId", - "Client", - "User", - "Language", - "Trace", - "IsoLanguage", - "Codepage", - "PartnerCodepage", - "RfcRole", - "Type", - "PartnerType", - "Rel", - "PartnerRel", - "KernelRel", - "CpicConvId", - "ProgName", - "PartnerBytesPerChar", - "PartnerSystemCodepage", - "Reserved"} - - s := reflect.ValueOf(&a).Elem() - // check if all parameters returned - assert.Equal(t, 23, s.NumField()) - for i := 0; i < s.NumField(); i++ { - pname := s.Type().Field(i).Name - assert.Equal(t, true, isValueInList(pname, paramNames), pname) - //f := s.Field(i) - //fmt.Println(i, f.Type(), f, s.Type().Field(i).Name) + paramNames := map[string]struct{}{ + "Dest": struct{}{}, + "Host": struct{}{}, + "PartnerHost": struct{}{}, + "SysNumber": struct{}{}, + "SysId": struct{}{}, + "Client": struct{}{}, + "User": struct{}{}, + "Language": struct{}{}, + "Trace": struct{}{}, + "IsoLanguage": struct{}{}, + "Codepage": struct{}{}, + "PartnerCodepage": struct{}{}, + "RfcRole": struct{}{}, + "Type": struct{}{}, + "PartnerType": struct{}{}, + "Rel": struct{}{}, + "PartnerRel": struct{}{}, + "KernelRel": struct{}{}, + "CpicConvId": struct{}{}, + "ProgName": struct{}{}, + "PartnerBytesPerChar": struct{}{}, + "PartnerSystemCodepage": struct{}{}, + "partnerIP": struct{}{}, + "partnerIPv6": struct{}{}, } - // check some parameters - assert.Equal(t, strings.ToUpper(abapSystem().User), a.User) - assert.Equal(t, abapSystem().Sysnr, a.SysNumber) - assert.Equal(t, abapSystem().Client, a.Client) + + // check if all parameters returned + assert.Equal(t, len(a), len(paramNames)) + // and the content of some + assert.Equal(t, strings.ToUpper(abapSystem()["user"]), a["user"]) + assert.Equal(t, abapSystem()["sysnr"], a["sysNumber"]) + assert.Equal(t, abapSystem()["client"], a["client"]) c.Close() } func TestPing(t *testing.T) { fmt.Println("Connection test: Ping") c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } + assert.Nil(t, err) err = c.Ping() assert.Nil(t, err) c.Close() } -func TestAlive(t *testing.T) { - fmt.Println("Connection test: Alive") - c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } - a := c.Alive() - assert.True(t, a) - c.Close() - a = c.Alive() - assert.False(t, a) -} - func TestReopen(t *testing.T) { fmt.Println("Connection test: Reopen") c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } + assert.Nil(t, err) err = c.Reopen() assert.Nil(t, err) c.Close() } func TestConnectFromDest(t *testing.T) { - c, err := ConnectionFromDest("I64_2") - if err != nil { - return - } + fmt.Println("Connection test: Destination") + assert.Greater(t, len(os.Getenv("RFC_INI")), 0) + c, err := ConnectionFromDest("MME") + assert.Nil(t, err) + assert.NotNil(t, c) + c.Close() +} + +func TestConnectionEcho(t *testing.T) { + fmt.Println("connection test: Echo") + assert.Greater(t, len(os.Getenv("RFC_INI")), 0) + c, err := ConnectionFromDest("MME") + assert.Nil(t, err) assert.NotNil(t, c) + type importStruct struct { + XXX string + } + params := map[string]interface{}{ + "REQUTEXT": "Hällö", + } + r, err := c.Call("STFC_CONNECTION", params) assert.Nil(t, err) + assert.NotNil(t, r["ECHOTEXT"]) + assert.Equal(t, params["REQUTEXT"], r["ECHOTEXT"]) c.Close() } @@ -146,11 +134,8 @@ func TestConnectFromDest(t *testing.T) { func TestWrongUserConnect(t *testing.T) { fmt.Println("Connection Error: Logon") a := abapSystem() - a.User = "@!n0user" + a["user"] = "@!n0user" c, err := ConnectionFromParams(a) - if err != nil { - return - } assert.Nil(t, c) assert.NotNil(t, err) assert.Equal(t, "Connection could not be opened", err.(*RfcError).Description) @@ -162,15 +147,12 @@ func TestWrongUserConnect(t *testing.T) { func TestMissingAshostConnect(t *testing.T) { fmt.Println("Connection Error: Connection parameter missing") a := abapSystem() - a.Ashost = "" + a["ashost"] = "" c, err := ConnectionFromParams(a) - if err != nil { - return - } assert.Nil(t, c) assert.NotNil(t, err) assert.Equal(t, "Connection could not be opened", err.(*RfcError).Description) - assert.Equal(t, "Parameter ASHOST, GWHOST or MSHOST is missing.", err.(*RfcError).ErrorInfo.Message) + assert.Equal(t, "Parameter ASHOST, GWHOST, MSHOST or PORT is missing.", err.(*RfcError).ErrorInfo.Message) assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Code) assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Key) } @@ -181,9 +163,7 @@ func TestWrongParameter(t *testing.T) { XXX string } c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } + assert.Nil(t, err) r, err := c.Call("STFC_CONNECTION", importStruct{"wrong param"}) assert.Equal(t, map[string]interface{}(nil), r) assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Code) // todo: should be "20" ?? @@ -197,10 +177,9 @@ func TestWrongParameter(t *testing.T) { // func TestFunctionDescription(t *testing.T) { + fmt.Println("STFC: Get Function Description") c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } + assert.Nil(t, err) d, err := c.GetFunctionDescription("STFC_CONNECTION") assert.Nil(t, err) assert.Equal(t, "ECHOTEXT", d.Parameters[0].Name) @@ -209,11 +188,10 @@ func TestFunctionDescription(t *testing.T) { c.Close() } -func TestFunctionCall(t *testing.T) { +func TestTableRowAsStructure(t *testing.T) { + fmt.Println("STFC: Table rows as structure") c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } + assert.Nil(t, err) type importedStruct struct { RFCFLOAT float64 RFCCHAR1 string @@ -232,7 +210,7 @@ func TestFunctionCall(t *testing.T) { IMPORTSTRUCT importedStruct RFCTABLE []importedStruct } - importStruct := importedStruct{1.23456789, "A", "BC", "DEFG", 1, 2, 345, []byte{0, 11, 12}, time.Now(), time.Now(), "HELLÖ SÄP", "DATA222"} + importStruct := importedStruct{4.23456789, "A", "BC", "DEFG", 1, 2, 345, []byte{0, 11, 12}, time.Now(), time.Now(), "HELLÖ SÄP", "DATA222"} params := parameter{importStruct, []importedStruct{importStruct}} r, err := c.Call("STFC_STRUCTURE", params) assert.Nil(t, err) @@ -249,7 +227,7 @@ func TestFunctionCall(t *testing.T) { assert.Equal(t, importStruct.RFCINT1, echoStruct["RFCINT1"]) assert.Equal(t, importStruct.RFCINT2, echoStruct["RFCINT2"]) assert.Equal(t, importStruct.RFCINT4, echoStruct["RFCINT4"]) - //assert.Equal(t, importStruct.RFCHEX3, echoStruct["RFCHEX3"]) + assert.Equal(t, importStruct.RFCHEX3, echoStruct["RFCHEX3"]) assert.Equal(t, importStruct.RFCTIME.Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("150405")) assert.Equal(t, importStruct.RFCDATE.Format("20060102"), echoStruct["RFCDATE"].(time.Time).Format("20060102")) assert.Equal(t, importStruct.RFCDATA1, echoStruct["RFCDATA1"]) @@ -264,7 +242,7 @@ func TestFunctionCall(t *testing.T) { assert.Equal(t, importStruct.RFCINT1, echoTableLine["RFCINT1"]) assert.Equal(t, importStruct.RFCINT2, echoTableLine["RFCINT2"]) assert.Equal(t, importStruct.RFCINT4, echoTableLine["RFCINT4"]) - //assert.Equal(t, importStruct.RFCHEX3, echoTableLine["RFCHEX3"]) + assert.Equal(t, importStruct.RFCHEX3, echoTableLine["RFCHEX3"]) assert.Equal(t, importStruct.RFCTIME.Format("150405"), echoTableLine["RFCTIME"].(time.Time).Format("150405")) assert.Equal(t, importStruct.RFCDATE.Format("20060102"), echoTableLine["RFCDATE"].(time.Time).Format("20060102")) assert.Equal(t, importStruct.RFCDATA1, echoTableLine["RFCDATA1"]) @@ -272,11 +250,10 @@ func TestFunctionCall(t *testing.T) { c.Close() } -func TestStructPassedAsMap(t *testing.T) { +func TestTableRowAsMap(t *testing.T) { + fmt.Println("STFC: Table rows as saps") c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } + assert.Nil(t, err) params := map[string]interface{}{ "IMPORTSTRUCT": map[string]interface{}{ @@ -306,7 +283,7 @@ func TestStructPassedAsMap(t *testing.T) { assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"]) assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"]) assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"]) - //assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"]) + assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"]) assert.Equal(t, importStruct["RFCTIME"].(time.Time).Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("150405")) assert.Equal(t, importStruct["RFCDATE"].(time.Time).Format("20060102"), echoStruct["RFCDATE"].(time.Time).Format("20060102")) assert.Equal(t, importStruct["RFCDATA1"], echoStruct["RFCDATA1"]) @@ -314,15 +291,54 @@ func TestStructPassedAsMap(t *testing.T) { c.Close() } +func TestTableRowAsVariable(t *testing.T) { + fmt.Println("STFC: Table rows as single variables") + c, err := ConnectionFromParams(abapSystem()) + assert.Nil(t, err) + + // array of byte sequences + certTable := [][]byte{ + []byte("ABC"), + []byte("DEF"), + } + params := map[string]interface{}{ + "IT_CERTLIST": certTable, + } + r, err := c.Call("SSFR_PSE_CREATE", params) + assert.Nil(t, err) + bapiret := r["ET_BAPIRET2"].([]interface{})[0].(map[string]interface{}) + assert.Equal(t, bapiret["ID"], "1S") + assert.Equal(t, bapiret["MESSAGE"], "Creating PSE failed (INITIAL)") + + // array of maps, works as well, as a workaround + certTableMap := []map[string]interface{}{ + map[string]interface{}{ + "": []byte("ABC"), + }, + map[string]interface{}{ + "": []byte("DEF"), + }, + } + params = map[string]interface{}{ + "IT_CERTLIST": certTableMap, + } + r, err = c.Call("SSFR_PSE_CREATE", params) + assert.Nil(t, err) + // same error message + bapiret = r["ET_BAPIRET2"].([]interface{})[0].(map[string]interface{}) + assert.Equal(t, bapiret["ID"], "1S") + assert.Equal(t, bapiret["MESSAGE"], "Creating PSE failed (INITIAL)") + c.Close() +} + func TestConfigParameter(t *testing.T) { + fmt.Println("STFC: Connection options: rstrip, returnImportParams") //rstrip = false c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } + assert.Nil(t, err) c.RStrip(false) r, _ := c.Call("STFC_CONNECTION", map[string]interface{}{"REQUTEXT": "HELLÖ SÄP"}) - assert.Equal(t, 255, len(reflect.ValueOf(r["ECHOTEXT"]).String())) + assert.Equal(t, 257, len(reflect.ValueOf(r["ECHOTEXT"]).String())) assert.Equal(t, "HELLÖ SÄP", strings.TrimSpace(reflect.ValueOf(r["ECHOTEXT"]).String())) //returnImportParams = true @@ -334,11 +350,9 @@ func TestConfigParameter(t *testing.T) { } func TestInvalidParameterFunctionCall(t *testing.T) { + fmt.Println("STFC: Invalid RFM parameter") c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } - + assert.Nil(t, err) r, err := c.Call("STFC_CONNECTION", map[string]interface{}{"XXX": "wrongParameter"}) assert.Nil(t, r) assert.NotNil(t, err) @@ -349,11 +363,14 @@ func TestInvalidParameterFunctionCall(t *testing.T) { c.Close() } +// +// Error test +// + func TestErrorFunctionCall(t *testing.T) { + fmt.Println("Error: ABAP message") c, err := ConnectionFromParams(abapSystem()) - if err != nil { - return - } + assert.Nil(t, err) r, err := c.Call("RFC_RAISE_ERROR", map[string]interface{}{"MESSAGETYPE": "A"}) assert.Nil(t, r) @@ -369,15 +386,13 @@ func TestErrorFunctionCall(t *testing.T) { c.Close() } -func abapSystem() ConnectionParameter { - return ConnectionParameter{ - Dest: "I64", - Client: "800", - User: "demo", - Passwd: "welcome", - Lang: "EN", - Ashost: "10.117.24.158 ", - Sysnr: "00", - Saprouter: "/H/203.13.155.17/W/xjkb3d/H/172.19.138.120/H/", +func abapSystem() ConnectionParameters { + return ConnectionParameters{ + "user": "demo", + "passwd": "welcome", + "ashost": "10.68.110.51", + "sysnr": "00", + "client": "620", + "lang": "EN", } } diff --git a/gorfc/sapnwrfc.ini b/gorfc/sapnwrfc.ini new file mode 100644 index 0000000..c973f25 --- /dev/null +++ b/gorfc/sapnwrfc.ini @@ -0,0 +1,7 @@ +DEST=MME +USER=demo +PASSWD=welcome +ASHOST=10.68.110.51 +SYSNR=00 +CLIENT=620 +LANG=EN \ No newline at end of file