├── .gitignore ├── .reuse └── dep5 ├── CHANGES ├── LICENSE ├── LICENSES └── Apache-2.0.txt ├── Makefile ├── README.md ├── doc └── README.md ├── example └── hello_gorfc.go ├── go.mod ├── go.sum └── gorfc ├── .editorconfig ├── gorfc.go ├── gorfc_test.go ├── sapnwrfc.ini └── testutils └── testutils.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | 25 | # RFC traces 26 | *.trc 27 | *.log 28 | 29 | # sapnwrfc.ini example 30 | # gorfc/sapnwrfc.ini 31 | 32 | # Packages 33 | GoRFC/pkg/ 34 | 35 | # Dependencies 36 | stretchr 37 | 38 | # others 39 | .DS_Store 40 | Thumbs.db 41 | *.tmp 42 | 43 | 44 | ### JetBrains 45 | *.iml 46 | .idea 47 | 48 | ### VSCode 49 | .vscode 50 | *.code-workspace 51 | 52 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: gorfc 3 | Upstream-Contact: Arno Uhlig , Fabian Ruff 4 | Source: https://github.com/SAP/gorfc 5 | 6 | Files: * 7 | Copyright: 2016-21 SAP SE or an SAP affiliate company and gorfc contributors 8 | License: Apache-2.0 -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | CHANGES 2 | ======= 3 | 4 | 0.0.1 (2016-02-01) 5 | ------------------ 6 | - Initial release 7 | 8 | 0.1.0 (2020-05-04) 9 | ------------------ 10 | - Darwin support 11 | - New connection parameters added, type changed to map 12 | - New connection attributes added, type changed to map 13 | - Table parameter accepts also array of variables 14 | - ABAP datatypes conversions fixed: 15 | - Bytes: RFCTYPE_BYTE, RFCTYPE_XSTRING 16 | - BCD: RFCTYPE_BCD 17 | - ABAP INT8 type added: RFCTYPE_INT8 18 | 19 | 0.2.0 (2020-06-09) 20 | ------------------ 21 | - ABAP datatypes support completed, added: RFC_DECF16, RFC_DECF34, RFC_UTCLONG 22 | - Error handling extended with GoRfc error type 23 | - RFCTYPE_XSTRING wrap fix, fixed cLen replaced by variable strLen 24 | - ABAP input parameters check extended, covering: 25 | - Passing non-array to TABLE parameter 26 | - Datatypes unit tests 27 | - Raise an error if RFM call requested over closed connection 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | Copyright 2016-21 SAP SE or an SAP affiliate company and gorfc contributors. 179 | 180 | Licensed under the Apache License, Version 2.0 (the "License"); 181 | you may not use this file except in compliance with the License. 182 | You may obtain a copy of the License at 183 | 184 | http://www.apache.org/licenses/LICENSE-2.0 185 | 186 | Unless required by applicable law or agreed to in writing, software 187 | distributed under the License is distributed on an "AS IS" BASIS, 188 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 189 | See the License for the specific language governing permissions and 190 | limitations under the License. 191 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 10 | 11 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 12 | 13 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 14 | 15 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 16 | 17 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 18 | 19 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 20 | 21 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 22 | 23 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 24 | 25 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 26 | 27 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 28 | 29 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 30 | 31 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 32 | 33 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 34 | 35 | (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and 36 | 37 | (b) You must cause any modified files to carry prominent notices stating that You changed the files; and 38 | 39 | (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 40 | 41 | (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 42 | 43 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 44 | 45 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 46 | 47 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 48 | 49 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 50 | 51 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 52 | 53 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 54 | 55 | END OF TERMS AND CONDITIONS 56 | 57 | APPENDIX: How to apply the Apache License to your work. 58 | 59 | To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. 60 | 61 | Copyright [yyyy] [name of copyright owner] 62 | 63 | Licensed under the Apache License, Version 2.0 (the "License"); 64 | you may not use this file except in compliance with the License. 65 | You may obtain a copy of the License at 66 | 67 | http://www.apache.org/licenses/LICENSE-2.0 68 | 69 | Unless required by applicable law or agreed to in writing, software 70 | distributed under the License is distributed on an "AS IS" BASIS, 71 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 72 | See the License for the specific language governing permissions and 73 | limitations under the License. 74 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Run go fmt against code 2 | fmt: 3 | go fmt ./... 4 | goimports -w -local github.com/sap/gorfc . 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Deprecation Notice 2 | 3 | This public repository is no longer maintained. Please see [this issue](https://github.com/SAP/gorfc/issues/42) for details. 4 | 5 | ![](https://img.shields.io/badge/STATUS-NOT%20CURRENTLY%20MAINTAINED-red.svg?longCache=true&style=flat) 6 | 7 | --- 8 | 9 | # SAP NetWeawer RFC SDK client bindings for GO 10 | 11 | For more details on the SAP NetWeaver Remote Function Call (RFC) Software Development Kit (SDK) please [see its support page](https://support.sap.com/en/product/connectors/nwrfcsdk.html). 12 | 13 | [![license](https://img.shields.io/badge/license-Apache-blue.svg)](https://github.com/SAP/gorfc/blob/master/LICENSE) 14 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP/gorfc)](https://api.reuse.software/badge/github.com/SAP/gorfc) 15 | [![Go Report Card](https://goreportcard.com/badge/github.com/SAP/gorfc)](https://goreportcard.com/report/github.com/SAP/gorfc) 16 | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/SAP/gorfc/gorfc) 17 | 18 | ## Features 19 | 20 | - Stateless and stateful connections (multiple function calls in the same ABAP session / same context) 21 | - Automatic conversion between GO and ABAP datatypes 22 | 23 | ## Supported Platforms 24 | 25 | - macOS, Linux 26 | 27 | - Windows is not supported until the [#21](https://github.com/SAP/gorfc/issues/21#issuecomment-716469783) fixed 28 | 29 | ## Requirements 30 | 31 | ### All platforms 32 | 33 | - GOLANG [requirements](https://golang.org/doc/install#requirements) 34 | 35 | - 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) 36 | 37 | - 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. 38 | - SAP NWRFC SDK [overview](https://support.sap.com/en/product/connectors/nwrfcsdk.html) and [release notes](https://launchpad.support.sap.com/#/softwarecenter/object/0020000000340702020) 39 | 40 | - 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) 41 | 42 | ### Windows 43 | 44 | - [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) 45 | 46 | - 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 47 | 48 | - Install the latest MinGW-w64, keeping offered defaults 49 | 50 | ### macOS 51 | 52 | - Build toolchain requires GCC and Xcode Command Line Tools: 53 | 54 | ```shell 55 | $ xcode-select --install 56 | ``` 57 | 58 | - The macOS firewall stealth mode must be disabled ([Can't ping a machine - why?](https://discussions.apple.com/thread/2554739)): 59 | 60 | ```shell 61 | sudo /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode off 62 | ``` 63 | 64 | - Remote paths must be set in SAP NWRFC SDK for macOS: [documentation](http://sap.github.io/PyRFC/install.html#macos) 65 | 66 | - 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) 67 | 68 | - Optionally: [valgrind](https://stackoverflow.com/questions/58360093/how-to-install-valgrind-on-macos-catalina-10-15-with-homebrew) 69 | 70 | ## SPJ articles 71 | 72 | Highly recommended reading about RFC communication and SAP NW RFC Library, published in the SAP Professional Journal (SPJ) 73 | 74 | - [Part I RFC Client Programming](https://wiki.scn.sap.com/wiki/x/zz27Gg) 75 | 76 | - [Part II RFC Server Programming](https://wiki.scn.sap.com/wiki/x/9z27Gg) 77 | 78 | - [Part III Advanced Topics](https://wiki.scn.sap.com/wiki/x/FD67Gg) 79 | 80 | ## Installation 81 | 82 | To start using SAP NW RFC Connector for Go, you shall: 83 | 84 | 1. [Install and Configure Go](#install-and-configure-go) 85 | 2. [Install the SAP NW RFC Library for your platform](#install-sap-nw-rfc-library) 86 | 3. [Install the GORFC package](#install-gorfc) 87 | 88 | ### Install and Configure Go 89 | 90 | If you are new to Go, the Go distribution shall be installed first, following [GO Installation](#ref1) and [GO Configuration](#ref2) instructions. See also [GO Environment Variables](#ref3). 91 | 92 | #### Windows Config Example 93 | 94 | After running the [MSI installer](https://golang.org/dl/), the default C:\Go folder is created and the _GOROOT_ system variable is set to C:\Go\. 95 | 96 | Create the Go work environment directory: 97 | 98 | ```shell 99 | cd c:\ 100 | mkdir workspace 101 | ``` 102 | 103 | Set the environment user varialbes GOPATH and GOBIN, add the bin subdirectories to PATH and restart the Windows shell. 104 | 105 | ```shell 106 | GOPATH = C:\workspace 107 | GOBIN = %GOPATH%\bin 108 | PATH = %GOROOT%\bin;%GOBIN%:%PATH% 109 | ``` 110 | 111 | See also [GO on Windows Example](#ref4). 112 | 113 | #### Linux 114 | 115 | The work environment setup works the same way like on Windows and [these instructions](https://github.com/golang/go/wiki/Ubuntu) describe the installation on Ubuntu Linux for example. 116 | 117 | ### Install SAP NW RFC Library 118 | 119 | To obtain and install _SAP NW RFC Library_ from _SAP Service Marketplace_, you can follow [the same instructions as for Python or nodejs RFC connectors](http://sap.github.io/PyRFC/install.html#install-c-connector). 120 | 121 | ### Install GORFC 122 | 123 | To install _gorfc_ and dependencies, run following commands: 124 | 125 | ```bash 126 | export CGO_CFLAGS="-I $SAPNWRFC_HOME/include" 127 | export CGO_LDFLAGS="-L $SAPNWRFC_HOME/lib" 128 | export CGO_CFLAGS_ALLOW=.* 129 | export CGO_LDFLAGS_ALLOW=.* 130 | go get github.com/stretchr/testify 131 | go get github.com/sap/gorfc 132 | cd $GOPATH/src/github.com/sap/gorfc/gorfc 133 | go build 134 | go install 135 | ``` 136 | 137 | To test the installation, run the example provided: 138 | 139 | ```bash 140 | cd $GOPATH/src/github.com/sap/gorfc/example 141 | go run hello_gorfc.go 142 | ``` 143 | 144 | ## Getting Started 145 | 146 | See the _hello_gorfc.go_ example and _gorfc_test.go_ unit tests. 147 | 148 | The GO RFC Connector follows the same principles and the implementation model of [Python](https://github.com/SAP/PyRFC) and [nodejs](https://github.com/SAP/node-rfc) RFC connectors and you may check examples and documentation there as well. 149 | 150 | ```go 151 | package main 152 | 153 | import ( 154 | "fmt" 155 | "github.com/sap/gorfc/gorfc" 156 | "github.com/stretchr/testify/assert" 157 | "reflect" 158 | "testing" 159 | "time" 160 | ) 161 | 162 | func abapSystem() gorfc.ConnectionParameter { 163 | return gorfc.ConnectionParameter{ 164 | Dest: "I64", 165 | Client: "800", 166 | User: "demo", 167 | Passwd: "welcome", 168 | Lang: "EN", 169 | Ashost: "11.111.11.111", 170 | Sysnr: "00", 171 | Saprouter: "/H/222.22.222.22/S/2222/W/xxxxx/H/222.22.222.222/H/", 172 | } 173 | } 174 | 175 | func main() { 176 | c, _ := gorfc.Connection(abapSystem()) 177 | var t *testing.T 178 | 179 | params := map[string]interface{}{ 180 | "IMPORTSTRUCT": map[string]interface{}{ 181 | "RFCFLOAT": 1.23456789, 182 | "RFCCHAR1": "A", 183 | "RFCCHAR2": "BC", 184 | "RFCCHAR4": "ÄBC", 185 | "RFCINT1": 0xfe, 186 | "RFCINT2": 0x7ffe, 187 | "RFCINT4": 999999999, 188 | "RFCHEX3": []byte{255, 254, 253}, 189 | "RFCTIME": time.Now(), 190 | "RFCDATE": time.Now(), 191 | "RFCDATA1": "HELLÖ SÄP", 192 | "RFCDATA2": "DATA222", 193 | }, 194 | } 195 | r, _ := c.Call("STFC_STRUCTURE", params) 196 | 197 | assert.NotNil(t, r["ECHOSTRUCT"]) 198 | importStruct := params["IMPORTSTRUCT"].(map[string]interface{}) 199 | echoStruct := r["ECHOSTRUCT"].(map[string]interface{}) 200 | assert.Equal(t, importStruct["RFCFLOAT"], echoStruct["RFCFLOAT"]) 201 | assert.Equal(t, importStruct["RFCCHAR1"], echoStruct["RFCCHAR1"]) 202 | assert.Equal(t, importStruct["RFCCHAR2"], echoStruct["RFCCHAR2"]) 203 | assert.Equal(t, importStruct["RFCCHAR4"], echoStruct["RFCCHAR4"]) 204 | assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"]) 205 | assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"]) 206 | assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"]) 207 | // assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"]) 208 | assert.Equal(t, importStruct["RFCTIME"].(time.Time).Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("15. 209 | assert.Equal(t, importStruct["RFCDATE"].(time.Time).Format("20060102"), e/Users/d037732/Downloads/gorfc/README.mdchoStruct["RFCDATE"].(time.Time).Format(". 210 | assert.Equal(t, importStruct["RFCDATA1"], echoStruct["RFCDATA1"]) 211 | assert.Equal(t, importStruct["RFCDATA2"], echoStruct["RFCDATA2"]) 212 | 213 | fmt.Println(reflect.TypeOf(importStruct["RFCDATE"])) 214 | fmt.Println(reflect.TypeOf(importStruct["RFCTIME"])) 215 | 216 | c.Close() 217 | ``` 218 | 219 | ## Licensing 220 | 221 | Please see our [LICENSE file](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available via the [REUSE tool](https://api.reuse.software/info/github.com/SAP/gorfc). 222 | 223 | ## References 224 | 225 | - [GO Installation](https://golang.org/doc/install) 226 | - [GO Configuration](https://golang.org/doc/code.html) 227 | - [GO Environment Variables](https://golang.org/cmd/go/#hdr-Environment_variables) 228 | - [GO on Windows Example](http://www.wadewegner.com/2014/12/easy-go-programming-setup-for-windows/) 229 | - [Another GO on Windows Example](https://github.com/abourget/getting-started-with-golang/blob/master/Getting_Started_for_Windows.md) 230 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | ABAP data types supported by RFC API are mapped to GO data types: 4 | 5 | | ABAP type | C typedef | Length (bytes) | Description | GO type | 6 | | --------- | ----------- | -------------- | ---------------------------------------------- | ------------------------------------- | 7 | | c | RFC_CHAR | 1-65535 | Characters, padded trailing blanks | string | 8 | | n | RFC_NUM | 1-65535 | Digits, fixed size, padded leading '0' | string | 9 | | x | RFC_BYTE | 1-65535 | Binary data | []byte | 10 | | p | RFC_BCD | 1-16 | BCD numbers (Binary Coded Decimals) | float64 or string, from abap: string | 11 | | i | RFC_INT | 4 | Integer | int32 | 12 | | b | RFC_INT1 | 1 | 1-byte integer, not directly supported by ABAP | uint8 | 13 | | s | RFC_INT2 | 2 | 2-byte integer, not directly supported by ABAP | int16 | 14 | | 8 | RFC_INT8 | 8 | 8-byte integer | int64 | 15 | | p | RFC_UTCLONG | 8 | Timestamp with high precision - 8 bytes | string | 16 | | f | RFC_FLOAT | 8 | Floating point numbers | float64 or string, from abap: float64 | 17 | | d | RFC_DATE | 8 or 16 | Date ("YYYYMMDD") | Time or string, from abap: Time | 18 | | t | RFC_TIME | 6 or 12 | Time ("HHMMSS") | Time or string, from abap: Time | 19 | | a | RFC_DECF16 | 8 | Decimal floating point 8 bytes (IEEE 754r) | float64 or string, from abap: string | 20 | | e | RFC_DECF34 | 16 | Decimal floating point 16 bytes (IEEE 754r) | float64 or string, from abap: string | 21 | | g | RFC_CHAR\* | | Variable-length, zero terminated string | string | 22 | | y | RFC_BYTE\* | | Variable-length raw string, length in bytes | []byte | 23 | -------------------------------------------------------------------------------- /example/hello_gorfc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "time" 7 | 8 | "github.com/sap/gorfc/gorfc" 9 | ) 10 | 11 | func abapSystem() gorfc.ConnectionParameters { 12 | return gorfc.ConnectionParameters{ 13 | "user": "demo", 14 | "passwd": "welcome", 15 | "ashost": "10.68.110.51", 16 | "sysnr": "00", 17 | "client": "620", 18 | "lang": "EN", 19 | } 20 | } 21 | 22 | func main() { 23 | c, err := gorfc.ConnectionFromParams(abapSystem()) 24 | if err != nil { 25 | fmt.Println(err) 26 | return 27 | } 28 | fmt.Println("Connected:", c.Alive()) 29 | 30 | attrs, _ := c.GetConnectionAttributes() 31 | fmt.Println("Connection attributes", attrs) 32 | 33 | params := map[string]interface{}{ 34 | "IMPORTSTRUCT": map[string]interface{}{ 35 | "RFCFLOAT": 1.23456789, 36 | "RFCCHAR1": "A", 37 | "RFCCHAR2": "BC", 38 | "RFCCHAR4": "ÄBC", 39 | "RFCINT1": 0xfe, 40 | "RFCINT2": 0x7ffe, 41 | "RFCINT4": 999999999, 42 | "RFCHEX3": []byte{255, 254, 253}, 43 | "RFCTIME": time.Now(), 44 | "RFCDATE": time.Now(), 45 | "RFCDATA1": "HELLÖ SÄP", 46 | "RFCDATA2": "DATA222", 47 | }, 48 | } 49 | r, e := c.Call("STFC_STRUCTURE", params) 50 | 51 | if e != nil { 52 | fmt.Println(e) 53 | return 54 | } 55 | 56 | fmt.Println(r["ECHOSTRUCT"]) 57 | 58 | importStruct := params["IMPORTSTRUCT"].(map[string]interface{}) 59 | echoStruct := r["ECHOSTRUCT"].(map[string]interface{}) 60 | fmt.Println(echoStruct) 61 | 62 | // empty time 63 | fmt.Println(importStruct["RFCDATE"], reflect.TypeOf(importStruct["RFCDATE"])) 64 | fmt.Println(echoStruct["RFCDATE"], reflect.TypeOf(echoStruct["RFCDATE"])) 65 | 66 | // empty date 67 | fmt.Println(importStruct["RFCTIME"], reflect.TypeOf(importStruct["RFCTIME"])) 68 | fmt.Println(echoStruct["RFCTIME"], reflect.TypeOf(echoStruct["RFCTIME"])) 69 | 70 | c.Close() 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/sap/gorfc 2 | 3 | go 1.17 4 | 5 | require github.com/stretchr/testify v1.7.0 6 | 7 | require ( 8 | github.com/davecgh/go-spew v1.1.0 // indirect 9 | github.com/pmezard/go-difflib v1.0.0 // indirect 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /gorfc/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = false -------------------------------------------------------------------------------- /gorfc/gorfc.go: -------------------------------------------------------------------------------- 1 | //go:build (linux && cgo) || (amd64 && cgo) || (darwin && cgo) 2 | // +build linux,cgo amd64,cgo darwin,cgo 3 | 4 | // Package gorfc provides SAP NetWeawer RFC SDK client bindings for GO 5 | package gorfc 6 | 7 | /* 8 | 9 | // ~~~~ windows ~~~~ // 10 | 11 | #cgo windows CFLAGS: -D_CRT_NON_CONFORMING_SWPRINTFS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE -D_CONSOLE 12 | #cgo windows CFLAGS: -DSAPonNT -D_AFXDLL -DWIN32 -D_WIN32_WINNT=0x0502 -DWIN64 -D_AMD64_ 13 | #cgo windows CFLAGS: -DSAPwithUNICODE -DUNICODE -D_UNICODE 14 | #cgo windows CFLAGS: -DSAPwithTHREADS -D_ATL_ALLOW_CHAR_UNSIGNED -DSAP_PLATFORM_MAKENAME=ntintel 15 | #cgo windows CFLAGS: -DNDEBUG -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D__NO_MATH_INLINES 16 | #cgo windows CFLAGS: -O2 -g -pthread -pipe -m64 17 | #cgo windows CFLAGS: -mwindows -march=x86-64 18 | #cgo windows CFLAGS: -fno-strict-aliasing -fno-omit-frame-pointer -fexceptions -funsigned-char 19 | #cgo windows CFLAGS: -Wall -Wno-uninitialized -Wno-long-long 20 | #cgo windows CFLAGS: -Wcast-align -Wunused-variable 21 | // todo -EHs ? 22 | // todo -Gy ? -ffunction-sections -fdata-sections 23 | // todo MD ? -lpthread -lm 24 | // todo -nologo -W3 -Z7 -GL -O2 -Oy- /we4552 /we4700 /we4789 25 | 26 | #cgo windows CFLAGS: -IC:/Tools/nwrfcsdk/include/ 27 | #cgo windows LDFLAGS: -LC:/Tools/nwrfcsdk/lib/ -lsapnwrfc -llibsapucum 28 | 29 | #cgo windows LDFLAGS: -O2 -g -pthread -pie -fPIE 30 | #cgo windows LDFLAGS: -OPT:REF -LTCG 31 | // todo -NXCOMPAT -STACK:0x2000000 -SWAPRUN:NET -DEBUG -DEBUGTYPE:CV,FIXUP -MACHINE:amd64 -nologo 32 | 33 | // ~~~~ linux ~~~~ // 34 | 35 | #cgo linux CFLAGS: -DNDEBUG -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 36 | #cgo linux CFLAGS: -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS 37 | #cgo linux CFLAGS: -DSAPonUNIX -DSAPonLIN 38 | #cgo linux CFLAGS: -O2 -g -pthread -pipe -m64 39 | #cgo linux CFLAGS: -fno-strict-aliasing -fno-omit-frame-pointer -fexceptions -funsigned-char 40 | #cgo linux CFLAGS: -Wall -Wno-uninitialized -Wno-long-long 41 | #cgo linux CFLAGS: -Wcast-align -Wno-unused-variable 42 | 43 | #cgo linux CFLAGS: -I/usr/local/sap/nwrfcsdk/include 44 | #cgo linux LDFLAGS: -L/usr/local/sap/nwrfcsdk/lib -lsapnwrfc -lsapucum 45 | 46 | #cgo linux LDFLAGS: -O2 -g -pthread 47 | 48 | // ~~~~ darwin ~~~~ // 49 | 50 | #cgo darwin CFLAGS: -Wall -O2 -Wno-uninitialized -Wcast-align 51 | #cgo darwin CFLAGS: -DSAP_UC_is_wchar -DSAPwithUNICODE -D__NO_MATH_INLINES -DSAPwithTHREADS -DSAPonDARW 52 | #cgo darwin CFLAGS: -fexceptions -funsigned-char -fno-strict-aliasing -fPIC -pthread -std=c17 -mmacosx-version-min=10.15 53 | #cgo darwin CFLAGS: -fno-omit-frame-pointer 54 | 55 | #cgo darwin CFLAGS: -I/usr/local/sap/nwrfcsdk/include 56 | #cgo darwin LDFLAGS: -L/usr/local/sap/nwrfcsdk/lib -lsapnwrfc -lsapucum 57 | #cgo darwin LDFLAGS: -Wl,-rpath,/usr/local/sap/nwrfcsdk/lib 58 | 59 | #cgo darwin LDFLAGS: -O2 -g -pthread 60 | #cgo darwin LDFLAGS: -stdlib=libc++ 61 | #cgo darwin LDFLAGS: -mmacosx-version-min=10.15 62 | 63 | #include 64 | 65 | static SAP_UC* GoMallocU(unsigned size) { 66 | return (SAP_UC*)(mallocU(size)); 67 | } 68 | 69 | static unsigned GoStrlenU(SAP_UTF16 *str) { 70 | return strlenU(str); 71 | } 72 | 73 | */ 74 | import "C" 75 | 76 | import ( 77 | "fmt" 78 | "reflect" 79 | "runtime" 80 | "strings" 81 | "time" 82 | "unsafe" 83 | ) 84 | 85 | /* 86 | static uint GoStrlenU(SAP_UTF16 *str) { 87 | return strlenU(str); 88 | } 89 | 90 | static uint GoStrlenU16(SAP_UTF16 *str) { 91 | return strlenU16(str); 92 | } 93 | 94 | static SAP_UC* GoMemsetU(SAP_UTF16 * s, int c, size_t n) { 95 | return (SAP_UC *)memsetU(s, c, n); 96 | } 97 | 98 | static uint GoStrlenU(SAP_UTF16 *str) { 99 | return strlenU(str); 100 | } 101 | */ 102 | 103 | //################################################################################ 104 | //# ERRORS # 105 | //################################################################################ 106 | 107 | // RfcError is returned by SAP NWRFC SDK 108 | type RfcError struct { 109 | Description string 110 | ErrorInfo rfcSDKError 111 | } 112 | 113 | func (err RfcError) Error() string { 114 | return fmt.Sprintf("NWRFC SDK error: %s | %s", err.Description, err.ErrorInfo) 115 | } 116 | 117 | func rfcError(errorInfo C.RFC_ERROR_INFO, format string, a ...interface{}) *RfcError { 118 | return &RfcError{fmt.Sprintf(format, a...), wrapError(&errorInfo)} 119 | } 120 | 121 | // GoRfcError is returned by gorfc 122 | type GoRfcError struct { 123 | Description string 124 | GoError error 125 | } 126 | 127 | func (err GoRfcError) Error() string { 128 | if err.GoError != nil { 129 | return fmt.Sprintf("GORFC error: %s | %s", err.Description, err.GoError.Error()) 130 | } 131 | return fmt.Sprintf("GORFC error: %s", err.Description) 132 | } 133 | 134 | func goRfcError(description string, goerror error) *GoRfcError { 135 | return &GoRfcError{description, goerror} 136 | } 137 | 138 | //################################################################################ 139 | //# FILL FUNCTIONS # 140 | //################################################################################ 141 | //# Fill functions take Go values and return C values 142 | 143 | // fillString allocates memory for the return value that has to be freed 144 | func fillString(gostr string) (sapuc *C.SAP_UC, err error) { 145 | var rc C.RFC_RC 146 | var errorInfo C.RFC_ERROR_INFO 147 | var resultLen C.uint 148 | sapucSize := C.uint(len(gostr) + 1) 149 | sapuc = C.GoMallocU(sapucSize) 150 | *sapuc = 0 151 | cStr := C.CString(gostr) 152 | defer C.free(unsafe.Pointer(cStr)) 153 | rc = C.RfcUTF8ToSAPUC((*C.RFC_BYTE)(cStr), C.uint(len(gostr)), sapuc, &sapucSize, &resultLen, &errorInfo) 154 | if rc != C.RFC_OK { 155 | err = rfcError(errorInfo, "Could not fill the string \"%v\"", gostr) 156 | } 157 | return 158 | } 159 | 160 | func fillFunctionParameter(funcDesc C.RFC_FUNCTION_DESC_HANDLE, container C.RFC_FUNCTION_HANDLE, goName string, value interface{}) (err error) { 161 | var rc C.RFC_RC 162 | var errorInfo C.RFC_ERROR_INFO 163 | var paramDesc C.RFC_PARAMETER_DESC 164 | var name *C.SAP_UC 165 | name, err = fillString(goName) 166 | defer C.free(unsafe.Pointer(name)) 167 | if err != nil { 168 | return 169 | } 170 | 171 | rc = C.RfcGetParameterDescByName(funcDesc, name, ¶mDesc, &errorInfo) 172 | if rc != C.RFC_OK { 173 | return rfcError(errorInfo, "Could not get the parameter description for \"%v\"", goName) 174 | } 175 | 176 | return fillVariable(paramDesc._type, container, (*C.SAP_UC)(¶mDesc.name[0]), value, paramDesc.typeDescHandle) 177 | } 178 | 179 | func fillVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP_UC, value interface{}, typeDesc C.RFC_TYPE_DESC_HANDLE) (err error) { 180 | var rc C.RFC_RC 181 | var errorInfo C.RFC_ERROR_INFO 182 | var structure C.RFC_STRUCTURE_HANDLE 183 | var table C.RFC_TABLE_HANDLE 184 | var cValue *C.SAP_UC 185 | var bValue *C.SAP_RAW 186 | 187 | defer C.free(unsafe.Pointer(cValue)) 188 | defer C.free(unsafe.Pointer(bValue)) 189 | 190 | switch cType { 191 | case C.RFCTYPE_STRUCTURE: 192 | rc = C.RfcGetStructure(container, cName, &structure, &errorInfo) 193 | if rc != C.RFC_OK { 194 | return rfcError(errorInfo, "Could not get structure") 195 | } 196 | err = fillStructure(typeDesc, structure, value) 197 | case C.RFCTYPE_TABLE: 198 | if reflect.TypeOf(value).String()[:1] != "[" { 199 | return goRfcError(fmt.Sprintf("GO %s passed to ABAP TABLE parameter, expected GO array", reflect.TypeOf(value).String()), nil) 200 | } 201 | rc = C.RfcGetTable(container, cName, &table, &errorInfo) 202 | if rc != C.RFC_OK { 203 | return rfcError(errorInfo, "Could not get table") 204 | } 205 | err = fillTable(typeDesc, table, value) 206 | case C.RFCTYPE_BYTE: 207 | bValue = (*C.SAP_RAW)(C.CBytes(reflect.ValueOf(value).Bytes())) 208 | cLen := C.uint(len(reflect.ValueOf(value).Bytes())) 209 | rc = C.RfcSetBytes(container, cName, bValue, cLen, &errorInfo) 210 | case C.RFCTYPE_XSTRING: 211 | bValue = (*C.SAP_RAW)(C.CBytes(reflect.ValueOf(value).Bytes())) 212 | cLen := C.uint(len(reflect.ValueOf(value).Bytes())) 213 | rc = C.RfcSetXString(container, cName, bValue, cLen, &errorInfo) 214 | case C.RFCTYPE_CHAR: 215 | cValue, err = fillString(reflect.ValueOf(value).String()) 216 | //cLen := C.uint(len(reflect.ValueOf(value).String())) 217 | cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) 218 | rc = C.RfcSetChars(container, cName, (*C.RFC_CHAR)(cValue), cLen, &errorInfo) 219 | case C.RFCTYPE_STRING: 220 | cValue, err = fillString(reflect.ValueOf(value).String()) 221 | //cLen := C.uint(len(reflect.ValueOf(value).String())) 222 | cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) 223 | rc = C.RfcSetString(container, cName, cValue, cLen, &errorInfo) 224 | case C.RFCTYPE_NUM: 225 | cValue, err = fillString(reflect.ValueOf(value).String()) 226 | //cLen := C.uint(len(reflect.ValueOf(value).String())) 227 | cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) 228 | rc = C.RfcSetNum(container, cName, (*C.RFC_NUM)(cValue), cLen, &errorInfo) 229 | //case C.RFCTYPE_BCD, C.RFCTYPE_DECF16, C.RFCTYPE_DECF34: 230 | // cValue, err = fillString(reflect.ValueOf(value).String()) 231 | // //cLen := C.uint(len(reflect.ValueOf(value).String())) 232 | // cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) 233 | // rc = C.RfcSetString(container, cName, cValue, cLen, &errorInfo) 234 | case C.RFCTYPE_FLOAT, C.RFCTYPE_BCD, C.RFCTYPE_DECF16, C.RFCTYPE_DECF34: 235 | var goVal string 236 | if reflect.TypeOf(value).Kind() == reflect.Float64 { 237 | goVal = fmt.Sprintf("%g", reflect.ValueOf(value).Float()) 238 | } else { 239 | goVal = reflect.ValueOf(value).String() 240 | } 241 | cValue, err = fillString(goVal) 242 | cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) 243 | rc = C.RfcSetString(container, cName, cValue, cLen, &errorInfo) 244 | case C.RFCTYPE_INT1: 245 | if reflect.TypeOf(value).Kind() == reflect.Int { 246 | rc = C.RfcSetInt(container, cName, C.RFC_INT(reflect.ValueOf(value).Int()), &errorInfo) 247 | } else { 248 | rc = C.RfcSetInt(container, cName, C.RFC_INT(reflect.ValueOf(value).Uint()), &errorInfo) 249 | } 250 | case C.RFCTYPE_INT2, C.RFCTYPE_INT, C.RFCTYPE_INT8: 251 | rc = C.RfcSetInt(container, cName, C.RFC_INT(reflect.ValueOf(value).Int()), &errorInfo) 252 | case C.RFCTYPE_DATE: 253 | cValue, err = fillString(value.(time.Time).Format("20060102")) 254 | rc = C.RfcSetDate(container, cName, (*C.RFC_CHAR)(cValue), &errorInfo) 255 | case C.RFCTYPE_TIME: 256 | cValue, err = fillString(value.(time.Time).Format("150405")) 257 | rc = C.RfcSetTime(container, cName, (*C.RFC_CHAR)(cValue), &errorInfo) 258 | case C.RFCTYPE_UTCLONG: 259 | cValue, err = fillString(reflect.ValueOf(value).String()) 260 | //cLen := C.uint(len(reflect.ValueOf(value).String())) 261 | cLen := C.uint(C.GoStrlenU((*C.SAP_UTF16)(cValue))) 262 | rc = C.RfcSetString(container, cName, cValue, cLen, &errorInfo) 263 | default: 264 | var goName string 265 | goName, err = wrapString(cName, true) 266 | return rfcError(errorInfo, "Unknown RFC type %v when filling %v", cType, goName) 267 | } 268 | if rc != C.RFC_OK { 269 | var goName string 270 | goName, err = wrapString(cName, true) 271 | err = rfcError(errorInfo, "Could not fill %v of type %v", goName, cType) 272 | } 273 | return 274 | } 275 | 276 | func fillStructure(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_STRUCTURE_HANDLE, value interface{}) (err error) { 277 | var errorInfo C.RFC_ERROR_INFO 278 | s := reflect.ValueOf(value) 279 | 280 | if s.Type().Kind() == reflect.Map { 281 | // Table passed as array of maps 282 | keys := s.MapKeys() 283 | if len(keys) > 0 { 284 | if keys[0].Kind() == reflect.String { 285 | for _, nameValue := range keys { 286 | fieldName := nameValue.String() 287 | fieldValue := s.MapIndex(nameValue).Interface() 288 | err = fillStructureField(typeDesc, container, fieldName, fieldValue) 289 | } 290 | } else { 291 | return rfcError(errorInfo, "Could not fill structure passed as map with non-string keys") 292 | } 293 | } 294 | } else if s.Type().Kind() == reflect.Struct { 295 | // Table passed as array of structures 296 | for i := 0; i < s.NumField(); i++ { 297 | fieldName := s.Type().Field(i).Name 298 | fieldValue := s.Field(i).Interface() 299 | err = fillStructureField(typeDesc, container, fieldName, fieldValue) 300 | } 301 | } else { 302 | // Table passed as array of variables 303 | err = fillStructureField(typeDesc, container, "", s.Interface()) 304 | } 305 | return 306 | } 307 | 308 | func fillStructureField(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_STRUCTURE_HANDLE, fieldName string, fieldValue interface{}) (err error) { 309 | var rc C.RFC_RC 310 | var errorInfo C.RFC_ERROR_INFO 311 | var fieldDesc C.RFC_FIELD_DESC 312 | cName, err := fillString(fieldName) 313 | defer C.free(unsafe.Pointer(cName)) 314 | 315 | rc = C.RfcGetFieldDescByName(typeDesc, cName, &fieldDesc, &errorInfo) 316 | if rc != C.RFC_OK { 317 | return rfcError(errorInfo, "Could not get field description for \"%v\"", fieldName) 318 | } 319 | 320 | return fillVariable(fieldDesc._type, C.RFC_FUNCTION_HANDLE(container), (*C.SAP_UC)(&fieldDesc.name[0]), fieldValue, fieldDesc.typeDescHandle) 321 | } 322 | 323 | func fillTable(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_TABLE_HANDLE, lines interface{}) (err error) { 324 | var errorInfo C.RFC_ERROR_INFO 325 | var lineHandle C.RFC_STRUCTURE_HANDLE 326 | for i := 0; i < reflect.ValueOf(lines).Len(); i++ { 327 | line := reflect.ValueOf(lines).Index(i) 328 | lineHandle = C.RfcAppendNewRow(container, &errorInfo) 329 | if lineHandle == nil { 330 | return rfcError(errorInfo, "Could not append new row to table") 331 | } 332 | err = fillStructure(typeDesc, lineHandle, line.Interface()) 333 | } 334 | return 335 | } 336 | 337 | //################################################################################ 338 | //# WRAPPER FUNCTIONS # 339 | //################################################################################ 340 | //# Wrapper functions take C values and return Go values 341 | 342 | func wrapString(sapuc *C.SAP_UC, strip bool) (string, error) { 343 | //return nWrapString(sapuc, C.uint(C.strlenU((*C.ushort)(sapuc))), strip) 344 | return nWrapString(sapuc, C.uint(C.GoStrlenU((*C.SAP_UTF16)(sapuc))), strip) 345 | } 346 | 347 | func nWrapString(sapuc *C.SAP_UC, sapucLength C.uint, strip bool) (string, error) { 348 | var errorInfo C.RFC_ERROR_INFO 349 | var rc C.RFC_RC 350 | var resultLength C.uint 351 | 352 | if sapucLength == 0 { 353 | return "", nil 354 | } 355 | 356 | utf8size := C.uint(5*sapucLength + 1) 357 | utf8Str := (*C.RFC_BYTE)(C.malloc((C.size_t)(utf8size))) 358 | defer C.free(unsafe.Pointer(utf8Str)) 359 | 360 | rc = C.RfcSAPUCToUTF8(sapuc, C.uint(sapucLength), utf8Str, &utf8size, &resultLength, &errorInfo) 361 | if rc != C.RFC_OK { 362 | return "", fmt.Errorf("wrapString sapucLength %v utf8size %v", sapucLength, utf8size) 363 | } 364 | //result := C.GoString((*C.char)(unsafe.Pointer(utf8Str))) 365 | result := C.GoStringN((*C.char)(unsafe.Pointer(utf8Str)), C.int(resultLength)) 366 | if strip { 367 | result = strings.TrimRight(result, "\x00 ") 368 | } 369 | return result, nil 370 | } 371 | 372 | type rfcSDKError struct { 373 | Message string 374 | Code string 375 | Key string 376 | AbapMsgClass string 377 | AbapMsgType string 378 | AbapMsgNumber string 379 | AbapMsgV1 string 380 | AbapMsgV2 string 381 | AbapMsgV3 string 382 | AbapMsgV4 string 383 | } 384 | 385 | func wrapError(errorInfo *C.RFC_ERROR_INFO) rfcSDKError { 386 | message, _ := wrapString(&errorInfo.message[0], true) 387 | code, _ := wrapString(C.RfcGetRcAsString(errorInfo.code), true) 388 | key, _ := wrapString(&errorInfo.key[0], true) 389 | abapMsgClass, _ := wrapString(&errorInfo.abapMsgClass[0], true) 390 | abapMsgType, _ := wrapString(&errorInfo.abapMsgType[0], true) 391 | abapMsgNumber, _ := wrapString((*C.SAP_UC)(&errorInfo.abapMsgNumber[0]), true) 392 | abapMsgV1, _ := wrapString(&errorInfo.abapMsgV1[0], true) 393 | abapMsgV2, _ := wrapString(&errorInfo.abapMsgV2[0], true) 394 | abapMsgV3, _ := wrapString(&errorInfo.abapMsgV3[0], true) 395 | abapMsgV4, _ := wrapString(&errorInfo.abapMsgV4[0], true) 396 | 397 | return rfcSDKError{message, code, key, abapMsgClass, abapMsgType, abapMsgNumber, abapMsgV1, abapMsgV2, abapMsgV3, abapMsgV4} 398 | } 399 | 400 | func (err rfcSDKError) String() string { 401 | 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) 402 | } 403 | 404 | // ConnectionAttributes returned by getConnectionInfo() method 405 | type ConnectionAttributes map[string]string 406 | 407 | func wrapConnectionAttributes(attributes C.RFC_ATTRIBUTES, strip bool) (connAttr ConnectionAttributes, err error) { 408 | connAttr = make(map[string]string) 409 | 410 | dest, err := nWrapString(&attributes.dest[0], 64, strip) 411 | host, err := nWrapString(&attributes.host[0], 100, strip) 412 | partnerHost, err := nWrapString(&attributes.partnerHost[0], 100, strip) 413 | sysNumber, err := nWrapString(&attributes.sysNumber[0], 2, strip) 414 | sysId, err := nWrapString(&attributes.sysId[0], 8, strip) 415 | client, err := nWrapString(&attributes.client[0], 3, strip) 416 | user, err := nWrapString(&attributes.user[0], 12, strip) 417 | language, err := nWrapString(&attributes.language[0], 2, strip) 418 | trace, err := nWrapString(&attributes.trace[0], 1, strip) 419 | isoLanguage, err := nWrapString(&attributes.isoLanguage[0], 2, strip) 420 | codepage, err := nWrapString(&attributes.codepage[0], 4, strip) 421 | partnerCodepage, err := nWrapString(&attributes.partnerCodepage[0], 4, strip) 422 | rfcRole, err := nWrapString(&attributes.rfcRole[0], 1, strip) 423 | _type, err := nWrapString(&attributes._type[0], 1, strip) 424 | partnerType, err := nWrapString(&attributes.partnerType[0], 1, strip) 425 | rel, err := nWrapString(&attributes.rel[0], 4, strip) 426 | partnerRel, err := nWrapString(&attributes.partnerRel[0], 4, strip) 427 | kernelRel, err := nWrapString(&attributes.kernelRel[0], 4, strip) 428 | cpicConvId, err := nWrapString(&attributes.cpicConvId[0], 8, strip) 429 | progName, err := nWrapString(&attributes.progName[0], 128, strip) 430 | partnerBytesPerChar, err := nWrapString(&attributes.partnerBytesPerChar[0], 1, strip) 431 | partnerSystemCodepage, err := nWrapString(&attributes.partnerSystemCodepage[0], 4, strip) 432 | partnerIP, err := nWrapString(&attributes.partnerIP[0], 15, strip) 433 | partnerIPv6, err := nWrapString(&attributes.partnerIPv6[0], 45, strip) 434 | //reserved, err := nWrapString(&attributes.reserved[0], 17, strip) 435 | 436 | connAttr["dest"] = dest 437 | connAttr["host"] = host 438 | connAttr["partnerHost"] = partnerHost 439 | connAttr["sysNumber"] = sysNumber 440 | connAttr["sysId"] = sysId 441 | connAttr["client"] = client 442 | connAttr["user"] = user 443 | connAttr["language"] = language 444 | connAttr["trace"] = trace 445 | connAttr["isoLanguage"] = isoLanguage 446 | connAttr["codepage"] = codepage 447 | connAttr["partnerCodepage"] = partnerCodepage 448 | connAttr["rfcRole"] = rfcRole 449 | connAttr["type"] = _type 450 | connAttr["partnerType"] = partnerType 451 | connAttr["rel"] = rel 452 | connAttr["partnerRel"] = partnerRel 453 | connAttr["kernelRel"] = kernelRel 454 | connAttr["cpicConvId"] = cpicConvId 455 | connAttr["progName"] = progName 456 | connAttr["partnerBytesPerChar"] = partnerBytesPerChar 457 | connAttr["partnerSystemCodepage"] = partnerSystemCodepage 458 | connAttr["partnerIP"] = partnerIP 459 | connAttr["partnerIPv6"] = partnerIPv6 460 | 461 | return 462 | } 463 | 464 | // FieldDescription type 465 | type FieldDescription struct { 466 | Name string 467 | FieldType string 468 | NucLength uint 469 | NucOffset uint 470 | UcLength uint 471 | UcOffset uint 472 | Decimals uint 473 | TypeDesc TypeDescription 474 | } 475 | 476 | // TypeDescription type 477 | type TypeDescription struct { 478 | Name string 479 | NucLength uint 480 | UcLength uint 481 | Fields []FieldDescription 482 | } 483 | 484 | func wrapTypeDescription(typeDesc C.RFC_TYPE_DESC_HANDLE) (goTypeDesc TypeDescription, err error) { 485 | var rc C.RFC_RC 486 | var errorInfo C.RFC_ERROR_INFO 487 | var fieldDesc C.RFC_FIELD_DESC 488 | var nucLength, ucLength C.uint 489 | var i, fieldCount C.uint 490 | 491 | typeName := (*C.SAP_UC)(C.malloc((C.size_t)(40 + 1))) 492 | *typeName = 0 493 | defer C.free(unsafe.Pointer(typeName)) 494 | 495 | rc = C.RfcGetTypeName(typeDesc, (*C.RFC_CHAR)(typeName), &errorInfo) 496 | if rc != C.RFC_OK { 497 | return goTypeDesc, rfcError(errorInfo, "Failed getting type name") 498 | } 499 | 500 | name, err := wrapString(typeName, false) 501 | if err != nil { 502 | return 503 | } 504 | 505 | rc = C.RfcGetTypeLength(typeDesc, &nucLength, &ucLength, &errorInfo) 506 | if rc != C.RFC_OK { 507 | return goTypeDesc, rfcError(errorInfo, "Failed getting type(%v) length", name) 508 | } 509 | 510 | goTypeDesc = TypeDescription{Name: name, NucLength: uint(nucLength), UcLength: uint(ucLength)} 511 | 512 | rc = C.RfcGetFieldCount(typeDesc, &fieldCount, &errorInfo) 513 | if rc != C.RFC_OK { 514 | return goTypeDesc, rfcError(errorInfo, "Failed getting field count") 515 | } 516 | 517 | for i = 0; i < fieldCount; i++ { 518 | rc = C.RfcGetFieldDescByIndex(typeDesc, i, &fieldDesc, &errorInfo) 519 | if rc != C.RFC_OK { 520 | return goTypeDesc, rfcError(errorInfo, "Failed getting field by index(%v)", i) 521 | } 522 | 523 | var fieldName string 524 | var fieldType string 525 | fieldName, err = wrapString((*C.SAP_UC)(&fieldDesc.name[0]), false) 526 | fieldType, err = wrapString((*C.SAP_UC)(C.RfcGetTypeAsString(fieldDesc._type)), false) 527 | if err != nil { 528 | return 529 | } 530 | 531 | goFieldDesc := FieldDescription{ 532 | Name: fieldName, 533 | FieldType: fieldType, 534 | NucLength: uint(fieldDesc.nucLength), 535 | NucOffset: uint(fieldDesc.nucOffset), 536 | UcLength: uint(fieldDesc.ucLength), 537 | UcOffset: uint(fieldDesc.ucOffset), 538 | Decimals: uint(fieldDesc.decimals), 539 | } 540 | 541 | if fieldDesc.typeDescHandle != nil { 542 | goFieldDesc.TypeDesc, err = wrapTypeDescription(fieldDesc.typeDescHandle) 543 | if err != nil { 544 | return 545 | } 546 | } 547 | 548 | goTypeDesc.Fields = append(goTypeDesc.Fields, goFieldDesc) 549 | } 550 | 551 | return 552 | } 553 | 554 | // ParameterDescription type 555 | type ParameterDescription struct { 556 | Name string 557 | ParameterType string 558 | Direction string 559 | NucLength uint 560 | UcLength uint 561 | Decimals uint 562 | DefaultValue string 563 | ParameterText string 564 | Optional bool 565 | TypeDesc TypeDescription 566 | // ExtendedDescription interface{} //This field can be used by the application programmer (i.e. you) to store arbitrary extra information. 567 | } 568 | 569 | func (paramDesc ParameterDescription) String() string { 570 | return fmt.Sprintf("paramDesc(name= %v, paramType= %v, dir= %v, nucLen= %v, ucLen= %v, dec= %v, defValue= %v, paramText= %v, optional= %v, typeDesc= %v)", 571 | paramDesc.Name, paramDesc.ParameterType, paramDesc.Direction, paramDesc.NucLength, paramDesc.UcLength, paramDesc.Decimals, paramDesc.DefaultValue, paramDesc.ParameterText, paramDesc.Optional, paramDesc.TypeDesc) 572 | } 573 | 574 | // FunctionDescription type 575 | type FunctionDescription struct { 576 | Name string 577 | Parameters []ParameterDescription 578 | } 579 | 580 | func (funcDesc FunctionDescription) String() (result string) { 581 | result = fmt.Sprintf("FunctionDescription:\n Name: %v\n Parameters:\n", funcDesc.Name) 582 | for i := 0; i < len(funcDesc.Parameters); i++ { 583 | result += fmt.Sprintf(" %v\n", funcDesc.Parameters[i]) 584 | } 585 | return 586 | } 587 | 588 | func wrapFunctionDescription(funcDesc C.RFC_FUNCTION_DESC_HANDLE) (goFuncDesc FunctionDescription, err error) { 589 | var rc C.RFC_RC 590 | var errorInfo C.RFC_ERROR_INFO 591 | var funcName C.RFC_ABAP_NAME 592 | var i, paramCount C.uint 593 | var paramDesc C.RFC_PARAMETER_DESC 594 | 595 | rc = C.RfcGetFunctionName(funcDesc, &funcName[0], &errorInfo) 596 | if rc != C.RFC_OK { 597 | return goFuncDesc, rfcError(errorInfo, "Failed getting function name") 598 | } 599 | 600 | goFuncName, err := wrapString((*C.SAP_UC)(&funcName[0]), false) 601 | if err != nil { 602 | return 603 | } 604 | goFuncDesc = FunctionDescription{Name: goFuncName} 605 | 606 | rc = C.RfcGetParameterCount(funcDesc, ¶mCount, &errorInfo) 607 | if rc != C.RFC_OK { 608 | return goFuncDesc, rfcError(errorInfo, "Failed getting function(%v) parameter count", goFuncName) 609 | } 610 | 611 | for i = 0; i < paramCount; i++ { 612 | rc = C.RfcGetParameterDescByIndex(funcDesc, i, ¶mDesc, &errorInfo) 613 | if rc != C.RFC_OK { 614 | return goFuncDesc, rfcError(errorInfo, "Failed getting function(%v) parameter description by index(%v)", goFuncName, i) 615 | } 616 | 617 | optional := true 618 | if paramDesc.optional == 0 { 619 | optional = false 620 | } 621 | 622 | var paramName string 623 | var paramType string 624 | var paramDir string 625 | var paramDefaultVal string 626 | var paramText string 627 | paramName, err = wrapString((*C.SAP_UC)(¶mDesc.name[0]), false) 628 | paramType, err = wrapString((*C.SAP_UC)(C.RfcGetTypeAsString(paramDesc._type)), false) 629 | paramDir, err = wrapString((*C.SAP_UC)(C.RfcGetDirectionAsString(paramDesc.direction)), false) 630 | paramDefaultVal, err = wrapString((*C.SAP_UC)(¶mDesc.defaultValue[0]), false) 631 | paramText, err = wrapString((*C.SAP_UC)(¶mDesc.parameterText[0]), false) 632 | if err != nil { 633 | return 634 | } 635 | 636 | goParamDesc := ParameterDescription{ 637 | Name: paramName, 638 | ParameterType: paramType, 639 | Direction: paramDir, 640 | NucLength: uint(paramDesc.nucLength), 641 | UcLength: uint(paramDesc.ucLength), 642 | Decimals: uint(paramDesc.decimals), 643 | DefaultValue: paramDefaultVal, 644 | ParameterText: paramText, 645 | Optional: optional, 646 | } 647 | 648 | if paramDesc.typeDescHandle != nil { 649 | goParamDesc.TypeDesc, err = wrapTypeDescription(paramDesc.typeDescHandle) 650 | if err != nil { 651 | return 652 | } 653 | } 654 | 655 | goFuncDesc.Parameters = append(goFuncDesc.Parameters, goParamDesc) 656 | } 657 | 658 | return 659 | } 660 | 661 | func wrapVariable(cType C.RFCTYPE, container C.RFC_FUNCTION_HANDLE, cName *C.SAP_UC, cLen C.uint, typeDesc C.RFC_TYPE_DESC_HANDLE, strip bool) (result interface{}, err error) { 662 | var rc C.RFC_RC 663 | var errorInfo C.RFC_ERROR_INFO 664 | var structure C.RFC_STRUCTURE_HANDLE 665 | var table C.RFC_TABLE_HANDLE 666 | var charValue *C.RFC_CHAR 667 | var stringValue *C.SAP_UC 668 | var numValue *C.RFC_NUM 669 | var byteValue *C.SAP_RAW 670 | var floatValue C.RFC_FLOAT 671 | var intValue C.RFC_INT 672 | var int1Value C.RFC_INT1 673 | var int2Value C.RFC_INT2 674 | var int8Value C.RFC_INT8 675 | var dateValue *C.RFC_CHAR 676 | var timeValue *C.RFC_CHAR 677 | 678 | var resultLen, strLen C.uint 679 | 680 | switch cType { 681 | case C.RFCTYPE_STRUCTURE: 682 | rc = C.RfcGetStructure(container, cName, &structure, &errorInfo) 683 | if rc != C.RFC_OK { 684 | return result, rfcError(errorInfo, "Failed getting structure") 685 | } 686 | return wrapStructure(typeDesc, structure, strip) 687 | case C.RFCTYPE_TABLE: 688 | rc = C.RfcGetTable(container, cName, &table, &errorInfo) 689 | if rc != C.RFC_OK { 690 | return result, rfcError(errorInfo, "Failed getting table") 691 | } 692 | return wrapTable(typeDesc, table, strip) 693 | case C.RFCTYPE_CHAR: 694 | charValue = (*C.RFC_CHAR)(C.GoMallocU(cLen)) 695 | defer C.free(unsafe.Pointer(charValue)) 696 | 697 | rc = C.RfcGetChars(container, cName, charValue, cLen, &errorInfo) 698 | if rc != C.RFC_OK { 699 | return result, rfcError(errorInfo, "Failed getting chars") 700 | } 701 | return nWrapString((*C.SAP_UC)(charValue), cLen, strip) 702 | case C.RFCTYPE_STRING: 703 | rc = C.RfcGetStringLength(container, cName, &strLen, &errorInfo) 704 | if rc != C.RFC_OK { 705 | return result, rfcError(errorInfo, "Failed getting string length") 706 | } 707 | 708 | stringValue = (*C.SAP_UC)(C.GoMallocU(strLen + 1)) 709 | defer C.free(unsafe.Pointer(stringValue)) 710 | 711 | rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo) 712 | if rc != C.RFC_OK { 713 | return result, rfcError(errorInfo, "Failed getting string") 714 | } 715 | return wrapString(stringValue, strip) 716 | case C.RFCTYPE_NUM: 717 | numValue = (*C.RFC_NUM)(C.GoMallocU(cLen)) 718 | defer C.free(unsafe.Pointer(numValue)) 719 | 720 | rc = C.RfcGetNum(container, cName, numValue, cLen, &errorInfo) 721 | if rc != C.RFC_OK { 722 | return result, rfcError(errorInfo, "Failed getting num") 723 | } 724 | return nWrapString((*C.SAP_UC)(numValue), cLen, strip) 725 | case C.RFCTYPE_BYTE: 726 | byteValue = (*C.SAP_RAW)(C.malloc(C.size_t(cLen))) 727 | defer C.free(unsafe.Pointer(byteValue)) 728 | rc = C.RfcGetBytes(container, cName, byteValue, cLen, &errorInfo) 729 | if rc != C.RFC_OK { 730 | return result, rfcError(errorInfo, "Failed getting bytes") 731 | } 732 | return C.GoBytes(unsafe.Pointer(byteValue), C.int(cLen)), err 733 | case C.RFCTYPE_XSTRING: 734 | rc = C.RfcGetStringLength(container, cName, &strLen, &errorInfo) 735 | if rc != C.RFC_OK { 736 | return result, rfcError(errorInfo, "Failed getting xstring length") 737 | } 738 | 739 | byteValue = (*C.SAP_RAW)(C.malloc(C.size_t(strLen))) 740 | defer C.free(unsafe.Pointer(byteValue)) 741 | rc = C.RfcGetXString(container, cName, byteValue, strLen, &resultLen, &errorInfo) 742 | if rc != C.RFC_OK { 743 | return result, rfcError(errorInfo, "Failed getting xstring") 744 | } 745 | return C.GoBytes(unsafe.Pointer(byteValue), C.int(strLen)), err 746 | case C.RFCTYPE_BCD: 747 | // An upper bound for the length of the _string representation_ 748 | // of the BCD is given by (2*cLen)-1 (each digit is encoded in 4bit, 749 | // the first 4 bit are reserved for the sign) 750 | // Furthermore, a sign char, a decimal separator char may be present 751 | // => (2*cLen)+1 752 | strLen = 2*cLen + 1 753 | stringValue = C.GoMallocU(strLen + 1) 754 | rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo) 755 | if rc == 23 { 756 | //Buffer too small, use returned requried result length 757 | C.free(unsafe.Pointer(stringValue)) 758 | strLen = resultLen 759 | stringValue = C.GoMallocU(strLen + 1) 760 | 761 | rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo) 762 | if rc != C.RFC_OK { 763 | defer C.free(unsafe.Pointer(stringValue)) 764 | return result, rfcError(errorInfo, "Failed getting BCD") 765 | } 766 | } 767 | defer C.free(unsafe.Pointer(stringValue)) 768 | return wrapString(stringValue, strip) 769 | case C.RFCTYPE_DECF16, C.RFCTYPE_DECF34: 770 | // An upper bound for the length of the _string representation_ 771 | // of the BCD is given by (2*cLen)-1 (each digit is encoded in 4bit, 772 | // the first 4 bit are reserved for the sign) 773 | // Furthermore, a sign char, a decimal separator char may be present 774 | // => (2*cLen)+1 775 | // and exponent char, sign and exponent 776 | // => +9 777 | strLen = 2*cLen + 10 778 | stringValue = C.GoMallocU(strLen + 1) 779 | rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo) 780 | if rc == 23 { 781 | //Buffer too small, use returned requried result length 782 | C.free(unsafe.Pointer(stringValue)) 783 | strLen = resultLen 784 | stringValue = C.GoMallocU(strLen + 1) 785 | 786 | rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo) 787 | if rc != C.RFC_OK { 788 | defer C.free(unsafe.Pointer(stringValue)) 789 | return result, rfcError(errorInfo, "Failed getting DECF") 790 | } 791 | } 792 | defer C.free(unsafe.Pointer(stringValue)) 793 | return wrapString(stringValue, strip) 794 | case C.RFCTYPE_FLOAT: 795 | rc = C.RfcGetFloat(container, cName, &floatValue, &errorInfo) 796 | if rc != C.RFC_OK { 797 | return result, rfcError(errorInfo, "Failed getting FLOAT") 798 | } 799 | return float64(floatValue), err 800 | case C.RFCTYPE_INT: 801 | rc = C.RfcGetInt(container, cName, &intValue, &errorInfo) 802 | if rc != C.RFC_OK { 803 | return result, rfcError(errorInfo, "Failed getting INT") 804 | } 805 | return int32(intValue), err 806 | case C.RFCTYPE_INT1: 807 | rc = C.RfcGetInt1(container, cName, &int1Value, &errorInfo) 808 | if rc != C.RFC_OK { 809 | return result, rfcError(errorInfo, "Failed getting INT1") 810 | } 811 | return uint8(int1Value), err 812 | case C.RFCTYPE_INT2: 813 | rc = C.RfcGetInt2(container, cName, &int2Value, &errorInfo) 814 | if rc != C.RFC_OK { 815 | return result, rfcError(errorInfo, "Failed getting INT2") 816 | } 817 | return int16(int2Value), err 818 | case C.RFCTYPE_INT8: 819 | rc = C.RfcGetInt8(container, cName, &int8Value, &errorInfo) 820 | if rc != C.RFC_OK { 821 | return result, rfcError(errorInfo, "Failed getting INT8") 822 | } 823 | return int64(int8Value), err 824 | case C.RFCTYPE_DATE: 825 | dateValue = (*C.RFC_CHAR)(C.malloc(8)) 826 | defer C.free(unsafe.Pointer(dateValue)) 827 | 828 | rc = C.RfcGetDate(container, cName, dateValue, &errorInfo) 829 | if rc != C.RFC_OK { 830 | return result, rfcError(errorInfo, "Failed getting DATE") 831 | } 832 | value, _ := nWrapString((*C.SAP_UC)(dateValue), 8, false) 833 | if value == "00000000" || ' ' == value[1] || err != nil { 834 | return 835 | } 836 | goDate, err := time.Parse("20060102", value) 837 | if err != nil { 838 | return nil, goRfcError("Error parsing ABAP RFC_DATE field", err) 839 | } 840 | return goDate, err 841 | case C.RFCTYPE_TIME: 842 | timeValue = (*C.RFC_CHAR)(C.malloc(6)) 843 | defer C.free(unsafe.Pointer(timeValue)) 844 | 845 | rc = C.RfcGetTime(container, cName, timeValue, &errorInfo) 846 | if rc != C.RFC_OK { 847 | return result, rfcError(errorInfo, "Failed getting TIME") 848 | } 849 | value, _ := nWrapString((*C.SAP_UC)(timeValue), 6, false) 850 | goTime, err := time.Parse("150405", value) 851 | if err != nil { 852 | return nil, goRfcError("Error parsing ABAP RFC_TIME field", err) 853 | } 854 | return goTime, err 855 | case C.RFCTYPE_UTCLONG: 856 | resultLen = 0 857 | strLen = 27 858 | 859 | stringValue = (*C.SAP_UC)(C.GoMallocU(strLen + 1)) 860 | defer C.free(unsafe.Pointer(stringValue)) 861 | 862 | rc = C.RfcGetString(container, cName, stringValue, strLen+1, &resultLen, &errorInfo) 863 | 864 | if rc != C.RFC_OK { 865 | return result, rfcError(errorInfo, "Failed getting UTCLONG") 866 | } 867 | utc, _ := nWrapString(stringValue, strLen, strip) 868 | return utc[:19] + "." + utc[20:], err 869 | } 870 | return result, rfcError(errorInfo, "Unknown RFC type %d when wrapping variable", cType) 871 | } 872 | 873 | func wrapStructure(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_STRUCTURE_HANDLE, strip bool) (result map[string]interface{}, err error) { 874 | var errorInfo C.RFC_ERROR_INFO 875 | var i, fieldCount C.uint 876 | var fieldDesc C.RFC_FIELD_DESC 877 | 878 | rc := C.RfcGetFieldCount(typeDesc, &fieldCount, &errorInfo) 879 | if rc != C.RFC_OK { 880 | return result, rfcError(errorInfo, "Failed getting field count") 881 | } 882 | result = make(map[string]interface{}) 883 | for i = 0; i < fieldCount; i++ { 884 | rc = C.RfcGetFieldDescByIndex(typeDesc, i, &fieldDesc, &errorInfo) 885 | if rc != C.RFC_OK { 886 | return result, rfcError(errorInfo, "Failed getting field description by index(%v)", i) 887 | } 888 | var fieldName string 889 | fieldName, err = wrapString((*C.SAP_UC)(&fieldDesc.name[0]), strip) 890 | if err != nil { 891 | return 892 | } 893 | result[fieldName], err = wrapVariable(fieldDesc._type, C.RFC_FUNCTION_HANDLE(container), (*C.SAP_UC)(&fieldDesc.name[0]), fieldDesc.nucLength, fieldDesc.typeDescHandle, strip) 894 | if err != nil { 895 | return 896 | } 897 | } 898 | return 899 | } 900 | 901 | func wrapTable(typeDesc C.RFC_TYPE_DESC_HANDLE, container C.RFC_TABLE_HANDLE, strip bool) (result []interface{}, err error) { 902 | var errorInfo C.RFC_ERROR_INFO 903 | var i, lines C.uint 904 | 905 | rc := C.RfcGetRowCount(container, &lines, &errorInfo) 906 | if rc != C.RFC_OK { 907 | return result, rfcError(errorInfo, "Failed getting row count") 908 | } 909 | result = make([]interface{}, lines, lines) 910 | for i = 0; i < lines; i++ { 911 | rc = C.RfcMoveTo(container, i, &errorInfo) 912 | if rc != C.RFC_OK { 913 | return result, rfcError(errorInfo, "Failed getting moving cursor to index(%v)", i) 914 | } 915 | structHandle := C.RfcGetCurrentRow(container, &errorInfo) 916 | var line map[string]interface{} 917 | line, err = wrapStructure(typeDesc, structHandle, strip) 918 | if err != nil { 919 | return 920 | } 921 | result[i] = line 922 | } 923 | return 924 | } 925 | 926 | func wrapResult(funcDesc C.RFC_FUNCTION_DESC_HANDLE, container C.RFC_FUNCTION_HANDLE, filterParameterDirection C.RFC_DIRECTION, strip bool) (result map[string]interface{}, err error) { 927 | var errorInfo C.RFC_ERROR_INFO 928 | var i, paramCount C.uint 929 | var paramDesc C.RFC_PARAMETER_DESC 930 | 931 | rc := C.RfcGetParameterCount(funcDesc, ¶mCount, &errorInfo) 932 | if rc != C.RFC_OK { 933 | return result, rfcError(errorInfo, "Failed getting parameter count") 934 | } 935 | 936 | result = make(map[string]interface{}) 937 | for i = 0; i < paramCount; i++ { 938 | rc = C.RfcGetParameterDescByIndex(funcDesc, i, ¶mDesc, &errorInfo) 939 | if rc != C.RFC_OK { 940 | return result, rfcError(errorInfo, "Failed getting parameter decription by index(%v)", i) 941 | } 942 | if paramDesc.direction != filterParameterDirection { 943 | var fieldName string 944 | fieldName, err = wrapString((*C.SAP_UC)(¶mDesc.name[0]), strip) 945 | if err != nil { 946 | return 947 | } 948 | result[fieldName], err = wrapVariable(paramDesc._type, container, (*C.SAP_UC)(¶mDesc.name[0]), paramDesc.nucLength, paramDesc.typeDescHandle, strip) 949 | if err != nil { 950 | return 951 | } 952 | } 953 | } 954 | 955 | return 956 | } 957 | 958 | //################################################################################ 959 | //# NW RFC LIB FUNCTIONALITY # 960 | //################################################################################ 961 | 962 | // GetNWRFCLibVersion returnd the major version, minor version and patchlevel of the SAP NetWeaver RFC library used. 963 | func GetNWRFCLibVersion() (major, minor, patchlevel uint) { 964 | var cmaj, cmin, cpatch C.uint 965 | C.RfcGetVersion(&cmaj, &cmin, &cpatch) 966 | major = uint(cmaj) 967 | minor = uint(cmin) 968 | patchlevel = uint(cpatch) 969 | return 970 | } 971 | 972 | //################################################################################ 973 | //# CONNECTION # 974 | //################################################################################ 975 | 976 | // Connection Parameters 977 | type ConnectionParameters map[string]string 978 | 979 | // Client Connection 980 | type Connection struct { 981 | handle C.RFC_CONNECTION_HANDLE 982 | rstrip bool 983 | returnImportParams bool 984 | alive bool 985 | paramCount C.uint 986 | connParams []C.RFC_CONNECTION_PARAMETER 987 | connectionParams ConnectionParameters 988 | // tHandle C.RFC_TRANSACTION_HANDLE 989 | // active_transaction bool 990 | // uHandle C.RFC_UNIT_HANDLE 991 | // active_unit bool 992 | } 993 | 994 | func connectionFinalizer(conn *Connection) { 995 | for _, connParam := range conn.connParams { 996 | C.free(unsafe.Pointer(connParam.name)) 997 | C.free(unsafe.Pointer(connParam.value)) 998 | } 999 | } 1000 | 1001 | // ConnectionFromParams creates a new connection with the given connection parameters and tries to open it. 1002 | // Returns the connection if successfull, otherwise nil. 1003 | func ConnectionFromParams(connectionParams ConnectionParameters) (conn *Connection, err error) { 1004 | conn = new(Connection) 1005 | 1006 | conn.handle = nil 1007 | conn.rstrip = true 1008 | conn.returnImportParams = false 1009 | conn.alive = false 1010 | 1011 | runtime.SetFinalizer(conn, connectionFinalizer) 1012 | conn.paramCount = C.uint(len(connectionParams)) 1013 | conn.connectionParams = connectionParams 1014 | conn.connParams = make([]C.RFC_CONNECTION_PARAMETER, conn.paramCount, conn.paramCount) 1015 | i := 0 1016 | for name, value := range conn.connectionParams { 1017 | conn.connParams[i].name, err = fillString(name) 1018 | conn.connParams[i].value, err = fillString(value) 1019 | i++ 1020 | } 1021 | if err != nil { 1022 | return nil, err 1023 | } 1024 | 1025 | err = conn.Open() 1026 | if err != nil { 1027 | return nil, err 1028 | } 1029 | 1030 | return 1031 | } 1032 | 1033 | // ConnectionFromDest creates a new connection with just the dest system id. 1034 | func ConnectionFromDest(dest string) (conn *Connection, err error) { 1035 | return ConnectionFromParams(ConnectionParameters{"dest": dest}) 1036 | } 1037 | 1038 | // RStrip sets rstrip of the given connection to the passed parameter and returns the connection 1039 | // right strips strings returned from RFC call (default is true) 1040 | func (conn *Connection) RStrip(rstrip bool) *Connection { 1041 | conn.rstrip = rstrip 1042 | return conn 1043 | } 1044 | 1045 | // ReturnImportParams sets returnImportParams of the given connection to the passed parameter and returns the connection 1046 | func (conn *Connection) ReturnImportParams(returnImportParams bool) *Connection { 1047 | conn.returnImportParams = returnImportParams 1048 | return conn 1049 | } 1050 | 1051 | // Alive returns true if the connection is open else returns false. 1052 | func (conn *Connection) Alive() bool { 1053 | return conn.alive 1054 | } 1055 | 1056 | // Close closes the connection and sets alive to false. 1057 | func (conn *Connection) Close() (err error) { 1058 | var errorInfo C.RFC_ERROR_INFO 1059 | if conn.alive { 1060 | conn.alive = false 1061 | rc := C.RfcCloseConnection(conn.handle, &errorInfo) 1062 | if rc != C.RFC_OK { 1063 | return rfcError(errorInfo, "Connection could not be closed") 1064 | } 1065 | } 1066 | return 1067 | } 1068 | 1069 | // Open opens the connection and sets alive to true. 1070 | func (conn *Connection) Open() (err error) { 1071 | var errorInfo C.RFC_ERROR_INFO 1072 | conn.handle = C.RfcOpenConnection(&conn.connParams[0], conn.paramCount, &errorInfo) 1073 | if errorInfo.code != C.RFC_OK { 1074 | return rfcError(errorInfo, "Connection could not be opened") 1075 | } 1076 | conn.alive = true 1077 | return 1078 | } 1079 | 1080 | // Reopen closes and opens the connection. 1081 | func (conn *Connection) Reopen() (err error) { 1082 | err = conn.Close() 1083 | if err != nil { 1084 | return 1085 | } 1086 | err = conn.Open() 1087 | return 1088 | } 1089 | 1090 | // Ping pings the server which the client is connected to and does nothing with the error if one occurs. 1091 | func (conn *Connection) Ping() (err error) { 1092 | var errorInfo C.RFC_ERROR_INFO 1093 | if !conn.alive { 1094 | err = conn.Open() 1095 | if err != nil { 1096 | return 1097 | } 1098 | } 1099 | rc := C.RfcPing(conn.handle, &errorInfo) 1100 | if rc != C.RFC_OK { 1101 | return rfcError(errorInfo, "Server could not be pinged") 1102 | } 1103 | return 1104 | } 1105 | 1106 | // GetConnectionAttributes returns the wrapped connection attributes of the connection. 1107 | func (conn *Connection) GetConnectionAttributes() (connAttr ConnectionAttributes, err error) { 1108 | var errorInfo C.RFC_ERROR_INFO 1109 | var attributes C.RFC_ATTRIBUTES 1110 | 1111 | rc := C.RfcGetConnectionAttributes(conn.handle, &attributes, &errorInfo) 1112 | if rc != C.RFC_OK || errorInfo.code != C.RFC_OK { 1113 | return nil, rfcError(errorInfo, "Could not get connection attributes") 1114 | } 1115 | return wrapConnectionAttributes(attributes, conn.rstrip) 1116 | } 1117 | 1118 | // GetFunctionDescription returns the wrapped function description of the given function. 1119 | func (conn *Connection) GetFunctionDescription(goFuncName string) (goFuncDesc FunctionDescription, err error) { 1120 | var errorInfo C.RFC_ERROR_INFO 1121 | 1122 | funcName, err := fillString(goFuncName) 1123 | defer C.free(unsafe.Pointer(funcName)) 1124 | if err != nil { 1125 | return 1126 | } 1127 | 1128 | if !conn.alive { 1129 | err = conn.Open() 1130 | if err != nil { 1131 | return 1132 | } 1133 | } 1134 | 1135 | funcDesc := C.RfcGetFunctionDesc(conn.handle, funcName, &errorInfo) 1136 | if funcDesc == nil { 1137 | return goFuncDesc, rfcError(errorInfo, "Could not get function description for \"%v\"", goFuncName) 1138 | } 1139 | 1140 | return wrapFunctionDescription(funcDesc) 1141 | } 1142 | 1143 | // Call calls the given function with the given parameters and wraps the results returned. 1144 | func (conn *Connection) Call(goFuncName string, params interface{}) (result map[string]interface{}, err error) { 1145 | if !conn.alive { 1146 | return nil, goRfcError("Call() method requires an open connection", nil) 1147 | } 1148 | 1149 | var errorInfo C.RFC_ERROR_INFO 1150 | 1151 | funcName, err := fillString(goFuncName) 1152 | defer C.free(unsafe.Pointer(funcName)) 1153 | if err != nil { 1154 | return 1155 | } 1156 | 1157 | if !conn.alive { 1158 | err = conn.Open() 1159 | if err != nil { 1160 | return 1161 | } 1162 | } 1163 | 1164 | funcDesc := C.RfcGetFunctionDesc(conn.handle, funcName, &errorInfo) 1165 | if funcDesc == nil { 1166 | return result, rfcError(errorInfo, "Could not get function description for \"%v\"", funcName) 1167 | } 1168 | 1169 | funcCont := C.RfcCreateFunction(funcDesc, &errorInfo) 1170 | if funcCont == nil { 1171 | return result, rfcError(errorInfo, "Could not create function") 1172 | } 1173 | 1174 | defer C.RfcDestroyFunction(funcCont, nil) 1175 | 1176 | paramsValue := reflect.ValueOf(params) 1177 | if paramsValue.Type().Kind() == reflect.Map { 1178 | keys := paramsValue.MapKeys() 1179 | if len(keys) > 0 { 1180 | if keys[0].Kind() == reflect.String { 1181 | for _, nameValue := range keys { 1182 | fieldName := nameValue.String() 1183 | fieldValue := paramsValue.MapIndex(nameValue).Interface() 1184 | 1185 | err = fillFunctionParameter(funcDesc, funcCont, fieldName, fieldValue) 1186 | if err != nil { 1187 | return 1188 | } 1189 | } 1190 | } else { 1191 | return result, rfcError(errorInfo, "Could not fill parameters passed as map with non-string keys") 1192 | } 1193 | } 1194 | } else if paramsValue.Type().Kind() == reflect.Struct { 1195 | for i := 0; i < paramsValue.NumField(); i++ { 1196 | fieldName := paramsValue.Type().Field(i).Name 1197 | fieldValue := paramsValue.Field(i).Interface() 1198 | 1199 | err = fillFunctionParameter(funcDesc, funcCont, fieldName, fieldValue) 1200 | if err != nil { 1201 | return 1202 | } 1203 | } 1204 | } else { 1205 | return result, rfcError(errorInfo, "Parameters can only be passed as types map[string]interface{} or go-structures") 1206 | } 1207 | 1208 | rc := C.RfcInvoke(conn.handle, funcCont, &errorInfo) 1209 | 1210 | if rc != C.RFC_OK { 1211 | return result, rfcError(errorInfo, "Could not invoke function \"%v\"", goFuncName) 1212 | } 1213 | 1214 | if conn.returnImportParams { 1215 | return wrapResult(funcDesc, funcCont, (C.RFC_DIRECTION)(0), conn.rstrip) 1216 | } 1217 | return wrapResult(funcDesc, funcCont, C.RFC_IMPORT, conn.rstrip) 1218 | } 1219 | -------------------------------------------------------------------------------- /gorfc/gorfc_test.go: -------------------------------------------------------------------------------- 1 | package gorfc 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "github.com/stretchr/testify/assert" 13 | 14 | "github.com/sap/gorfc/gorfc/testutils" 15 | ) 16 | 17 | // 18 | // NW RFC Lib Version 19 | // 20 | func TestNWRFCLibVersion(t *testing.T) { 21 | major, minor, patchlevel := GetNWRFCLibVersion() 22 | assert.Equal(t, uint(7500), major) // adapt to your NW RFC Lib version 23 | assert.Equal(t, uint(0), minor) 24 | assert.Greater(t, patchlevel, uint(4)) 25 | } 26 | 27 | // 28 | // Connection Tests 29 | // 30 | func TestConnect(t *testing.T) { 31 | fmt.Println("Connection test: Open and Close") 32 | c, err := ConnectionFromParams(abapSystem()) 33 | if err != nil { 34 | t.SkipNow() 35 | } 36 | assert.NotNil(t, c) 37 | assert.Nil(t, err) 38 | assert.True(t, c.Alive()) 39 | c.Close() 40 | assert.False(t, c.Alive()) 41 | } 42 | 43 | func TestConnectionAttributes(t *testing.T) { 44 | fmt.Println("Connection test: Attributes") 45 | c, err := ConnectionFromParams(abapSystem()) 46 | assert.Equal(t, err, nil) 47 | 48 | a, err := c.GetConnectionAttributes() 49 | paramNames := map[string]struct{}{ 50 | "Dest": struct{}{}, 51 | "Host": struct{}{}, 52 | "PartnerHost": struct{}{}, 53 | "SysNumber": struct{}{}, 54 | "SysId": struct{}{}, 55 | "Client": struct{}{}, 56 | "User": struct{}{}, 57 | "Language": struct{}{}, 58 | "Trace": struct{}{}, 59 | "IsoLanguage": struct{}{}, 60 | "Codepage": struct{}{}, 61 | "PartnerCodepage": struct{}{}, 62 | "RfcRole": struct{}{}, 63 | "Type": struct{}{}, 64 | "PartnerType": struct{}{}, 65 | "Rel": struct{}{}, 66 | "PartnerRel": struct{}{}, 67 | "KernelRel": struct{}{}, 68 | "CpicConvId": struct{}{}, 69 | "ProgName": struct{}{}, 70 | "PartnerBytesPerChar": struct{}{}, 71 | "PartnerSystemCodepage": struct{}{}, 72 | "partnerIP": struct{}{}, 73 | "partnerIPv6": struct{}{}, 74 | } 75 | 76 | // check if all parameters returned 77 | assert.Equal(t, len(a), len(paramNames)) 78 | // and the content of some 79 | assert.Equal(t, strings.ToUpper(abapSystem()["user"]), a["user"]) 80 | assert.Equal(t, abapSystem()["sysnr"], a["sysNumber"]) 81 | assert.Equal(t, abapSystem()["client"], a["client"]) 82 | c.Close() 83 | } 84 | 85 | func TestPing(t *testing.T) { 86 | fmt.Println("Connection test: Ping") 87 | c, err := ConnectionFromParams(abapSystem()) 88 | assert.Nil(t, err) 89 | err = c.Ping() 90 | assert.Nil(t, err) 91 | c.Close() 92 | } 93 | 94 | func TestReopen(t *testing.T) { 95 | fmt.Println("Connection test: Reopen") 96 | c, err := ConnectionFromParams(abapSystem()) 97 | assert.Nil(t, err) 98 | err = c.Reopen() 99 | assert.Nil(t, err) 100 | c.Close() 101 | } 102 | 103 | func TestConnectFromDest(t *testing.T) { 104 | fmt.Println("Connection test: Destination") 105 | assert.Greater(t, len(os.Getenv("RFC_INI")), 0) 106 | c, err := ConnectionFromDest("MME") 107 | assert.Nil(t, err) 108 | assert.NotNil(t, c) 109 | c.Close() 110 | } 111 | 112 | func TestConnectionEcho(t *testing.T) { 113 | fmt.Println("connection test: Echo") 114 | assert.Greater(t, len(os.Getenv("RFC_INI")), 0) 115 | c, err := ConnectionFromDest("MME") 116 | assert.Nil(t, err) 117 | assert.NotNil(t, c) 118 | type importStruct struct { 119 | XXX string 120 | } 121 | params := map[string]interface{}{ 122 | "REQUTEXT": "Hällö", 123 | } 124 | r, err := c.Call("STFC_CONNECTION", params) 125 | assert.Nil(t, err) 126 | assert.NotNil(t, r["ECHOTEXT"]) 127 | assert.Equal(t, params["REQUTEXT"], r["ECHOTEXT"]) 128 | c.Close() 129 | } 130 | 131 | // 132 | // Connection Errors 133 | // 134 | 135 | func TestWrongUserConnect(t *testing.T) { 136 | fmt.Println("Connection Error: Logon") 137 | a := abapSystem() 138 | a["user"] = "@!n0user" 139 | c, err := ConnectionFromParams(a) 140 | assert.Nil(t, c) 141 | assert.NotNil(t, err) 142 | assert.Equal(t, "Connection could not be opened", err.(*RfcError).Description) 143 | assert.Equal(t, "Name or password is incorrect (repeat logon)", err.(*RfcError).ErrorInfo.Message) 144 | assert.Equal(t, "RFC_LOGON_FAILURE", err.(*RfcError).ErrorInfo.Code) 145 | assert.Equal(t, "RFC_LOGON_FAILURE", err.(*RfcError).ErrorInfo.Key) 146 | } 147 | 148 | func TestMissingAshostConnect(t *testing.T) { 149 | fmt.Println("Connection Error: Connection parameter missing") 150 | a := abapSystem() 151 | a["ashost"] = "" 152 | c, err := ConnectionFromParams(a) 153 | assert.Nil(t, c) 154 | assert.NotNil(t, err) 155 | assert.Equal(t, "Connection could not be opened", err.(*RfcError).Description) 156 | assert.Equal(t, "Parameter ASHOST, GWHOST, MSHOST or PORT is missing.", err.(*RfcError).ErrorInfo.Message) 157 | assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Code) 158 | assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Key) 159 | } 160 | 161 | func TestWrongParameter(t *testing.T) { 162 | fmt.Println("Connection Error: Call() with non-existing parameter") 163 | type importStruct struct { 164 | XXX string 165 | } 166 | c, err := ConnectionFromParams(abapSystem()) 167 | assert.Nil(t, err) 168 | r, err := c.Call("STFC_CONNECTION", importStruct{"wrong param"}) 169 | assert.Equal(t, map[string]interface{}(nil), r) 170 | assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Code) // todo: should be "20" ?? 171 | assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Key) 172 | assert.Equal(t, "field 'XXX' not found", err.(*RfcError).ErrorInfo.Message) 173 | c.Close() 174 | } 175 | 176 | func TestCallOverClosedConnection(t *testing.T) { 177 | fmt.Println("Connection Error: Call() over closed connection") 178 | c, err := ConnectionFromDest("MME") 179 | assert.Nil(t, err) 180 | c.Close() 181 | assert.False(t, c.Alive()) 182 | r, err := c.Call("STFC_CONNECTION", map[string]interface{}{"REQUTEXT": "HELLÖ SÄP"}) 183 | assert.Nil(t, r) 184 | assert.Equal(t, "Call() method requires an open connection", err.(*GoRfcError).Description) 185 | } 186 | 187 | // 188 | // STFC Tests 189 | // 190 | 191 | func TestFunctionDescription(t *testing.T) { 192 | fmt.Println("STFC: Get Function Description") 193 | c, err := ConnectionFromParams(abapSystem()) 194 | assert.Nil(t, err) 195 | d, err := c.GetFunctionDescription("STFC_CONNECTION") 196 | assert.Nil(t, err) 197 | assert.Equal(t, "ECHOTEXT", d.Parameters[0].Name) 198 | assert.Equal(t, "RESPTEXT", d.Parameters[1].Name) 199 | assert.Equal(t, "REQUTEXT", d.Parameters[2].Name) 200 | c.Close() 201 | } 202 | 203 | func TestTableRowAsStructure(t *testing.T) { 204 | fmt.Println("STFC: Table rows as structure") 205 | c, err := ConnectionFromParams(abapSystem()) 206 | assert.Nil(t, err) 207 | type importedStruct struct { 208 | RFCFLOAT float64 209 | RFCCHAR1 string 210 | RFCCHAR2 string 211 | RFCCHAR4 string 212 | RFCINT1 uint8 213 | RFCINT2 int16 214 | RFCINT4 int32 215 | RFCHEX3 []byte 216 | RFCTIME time.Time 217 | RFCDATE time.Time 218 | RFCDATA1 string 219 | RFCDATA2 string 220 | } 221 | type parameter struct { 222 | IMPORTSTRUCT importedStruct 223 | RFCTABLE []importedStruct 224 | } 225 | importStruct := importedStruct{4.23456789, "A", "BC", "DEFG", 1, 2, 345, []byte{0, 11, 12}, time.Now(), time.Now(), "HELLÖ SÄP", "DATA222"} 226 | params := parameter{importStruct, []importedStruct{importStruct}} 227 | r, err := c.Call("STFC_STRUCTURE", params) 228 | assert.Nil(t, err) 229 | assert.NotNil(t, r) 230 | 231 | assert.Nil(t, r["IMPORTSTUCT"]) 232 | 233 | assert.NotNil(t, r["ECHOSTRUCT"]) 234 | echoStruct := r["ECHOSTRUCT"].(map[string]interface{}) 235 | assert.Equal(t, importStruct.RFCFLOAT, echoStruct["RFCFLOAT"]) 236 | assert.Equal(t, importStruct.RFCCHAR1, echoStruct["RFCCHAR1"]) 237 | assert.Equal(t, importStruct.RFCCHAR2, echoStruct["RFCCHAR2"]) 238 | assert.Equal(t, importStruct.RFCCHAR4, echoStruct["RFCCHAR4"]) 239 | assert.Equal(t, importStruct.RFCINT1, echoStruct["RFCINT1"]) 240 | assert.Equal(t, importStruct.RFCINT2, echoStruct["RFCINT2"]) 241 | assert.Equal(t, importStruct.RFCINT4, echoStruct["RFCINT4"]) 242 | assert.Equal(t, importStruct.RFCHEX3, echoStruct["RFCHEX3"]) 243 | assert.Equal(t, importStruct.RFCTIME.Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("150405")) 244 | assert.Equal(t, importStruct.RFCDATE.Format("20060102"), echoStruct["RFCDATE"].(time.Time).Format("20060102")) 245 | assert.Equal(t, importStruct.RFCDATA1, echoStruct["RFCDATA1"]) 246 | assert.Equal(t, importStruct.RFCDATA2, echoStruct["RFCDATA2"]) 247 | 248 | assert.NotNil(t, r["RFCTABLE"]) 249 | echoTableLine := r["RFCTABLE"].([]interface{})[0].(map[string]interface{}) 250 | assert.Equal(t, importStruct.RFCFLOAT, echoTableLine["RFCFLOAT"]) 251 | assert.Equal(t, importStruct.RFCCHAR1, echoTableLine["RFCCHAR1"]) 252 | assert.Equal(t, importStruct.RFCCHAR2, echoTableLine["RFCCHAR2"]) 253 | assert.Equal(t, importStruct.RFCCHAR4, echoTableLine["RFCCHAR4"]) 254 | assert.Equal(t, importStruct.RFCINT1, echoTableLine["RFCINT1"]) 255 | assert.Equal(t, importStruct.RFCINT2, echoTableLine["RFCINT2"]) 256 | assert.Equal(t, importStruct.RFCINT4, echoTableLine["RFCINT4"]) 257 | assert.Equal(t, importStruct.RFCHEX3, echoTableLine["RFCHEX3"]) 258 | assert.Equal(t, importStruct.RFCTIME.Format("150405"), echoTableLine["RFCTIME"].(time.Time).Format("150405")) 259 | assert.Equal(t, importStruct.RFCDATE.Format("20060102"), echoTableLine["RFCDATE"].(time.Time).Format("20060102")) 260 | assert.Equal(t, importStruct.RFCDATA1, echoTableLine["RFCDATA1"]) 261 | assert.Equal(t, importStruct.RFCDATA2, echoTableLine["RFCDATA2"]) 262 | c.Close() 263 | } 264 | 265 | func TestTableRowAsMap(t *testing.T) { 266 | fmt.Println("STFC: Table rows as maps") 267 | c, err := ConnectionFromParams(abapSystem()) 268 | assert.Nil(t, err) 269 | 270 | params := map[string]interface{}{ 271 | "IMPORTSTRUCT": map[string]interface{}{ 272 | "RFCFLOAT": 1.23456789, 273 | "RFCCHAR1": "A", 274 | "RFCCHAR2": "BC", 275 | "RFCCHAR4": "ÄBC", 276 | "RFCINT1": uint8(0xfe), 277 | "RFCINT2": int16(0x7ffe), 278 | "RFCINT4": int32(999999999), 279 | "RFCHEX3": []byte{255, 254, 253}, 280 | "RFCTIME": time.Now(), 281 | "RFCDATE": time.Now(), 282 | "RFCDATA1": "HELLÖ SÄP", 283 | "RFCDATA2": "DATA222", 284 | }, 285 | } 286 | r, _ := c.Call("STFC_STRUCTURE", params) 287 | 288 | assert.NotNil(t, r["ECHOSTRUCT"]) 289 | importStruct := params["IMPORTSTRUCT"].(map[string]interface{}) 290 | echoStruct := r["ECHOSTRUCT"].(map[string]interface{}) 291 | assert.Equal(t, importStruct["RFCFLOAT"], echoStruct["RFCFLOAT"]) 292 | assert.Equal(t, importStruct["RFCCHAR1"], echoStruct["RFCCHAR1"]) 293 | assert.Equal(t, importStruct["RFCCHAR2"], echoStruct["RFCCHAR2"]) 294 | assert.Equal(t, importStruct["RFCCHAR4"], echoStruct["RFCCHAR4"]) 295 | assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"]) 296 | assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"]) 297 | assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"]) 298 | assert.Equal(t, importStruct["RFCHEX3"], echoStruct["RFCHEX3"]) 299 | assert.Equal(t, importStruct["RFCTIME"].(time.Time).Format("150405"), echoStruct["RFCTIME"].(time.Time).Format("150405")) 300 | assert.Equal(t, importStruct["RFCDATE"].(time.Time).Format("20060102"), echoStruct["RFCDATE"].(time.Time).Format("20060102")) 301 | assert.Equal(t, importStruct["RFCDATA1"], echoStruct["RFCDATA1"]) 302 | assert.Equal(t, importStruct["RFCDATA2"], echoStruct["RFCDATA2"]) 303 | c.Close() 304 | } 305 | 306 | func TestTableRowAsVariable(t *testing.T) { 307 | fmt.Println("STFC: Table rows as single variables") 308 | c, err := ConnectionFromParams(abapSystem()) 309 | assert.Nil(t, err) 310 | 311 | // array of byte sequences 312 | certTable := [][]byte{ 313 | []byte("ABC"), 314 | []byte("DEF"), 315 | } 316 | params := map[string]interface{}{ 317 | "IT_CERTLIST": certTable, 318 | } 319 | r, err := c.Call("SSFR_PSE_CREATE", params) 320 | assert.Nil(t, err) 321 | bapiret := r["ET_BAPIRET2"].([]interface{})[0].(map[string]interface{}) 322 | assert.Equal(t, bapiret["ID"], "1S") 323 | assert.Equal(t, bapiret["MESSAGE"], "Creating PSE failed (INITIAL)") 324 | 325 | // array of maps, works as well, as a workaround 326 | certTableMap := []map[string]interface{}{ 327 | map[string]interface{}{ 328 | "": []byte("ABC"), 329 | }, 330 | map[string]interface{}{ 331 | "": []byte("DEF"), 332 | }, 333 | } 334 | params = map[string]interface{}{ 335 | "IT_CERTLIST": certTableMap, 336 | } 337 | r, err = c.Call("SSFR_PSE_CREATE", params) 338 | assert.Nil(t, err) 339 | // same error message 340 | bapiret = r["ET_BAPIRET2"].([]interface{})[0].(map[string]interface{}) 341 | assert.Equal(t, bapiret["ID"], "1S") 342 | assert.Equal(t, bapiret["MESSAGE"], "Creating PSE failed (INITIAL)") 343 | c.Close() 344 | } 345 | 346 | func TestConfigParameter(t *testing.T) { 347 | fmt.Println("STFC: Connection options: rstrip, returnImportParams") 348 | //rstrip = false 349 | c, err := ConnectionFromParams(abapSystem()) 350 | assert.Nil(t, err) 351 | c.RStrip(false) 352 | r, _ := c.Call("STFC_CONNECTION", map[string]interface{}{"REQUTEXT": "HELLÖ SÄP"}) 353 | assert.Equal(t, 257, len(reflect.ValueOf(r["ECHOTEXT"]).String())) 354 | assert.Equal(t, "HELLÖ SÄP", strings.TrimSpace(reflect.ValueOf(r["ECHOTEXT"]).String())) 355 | 356 | //returnImportParams = true 357 | c, _ = ConnectionFromParams(abapSystem()) 358 | c.ReturnImportParams(true) 359 | r, _ = c.Call("STFC_CONNECTION", map[string]interface{}{"REQUTEXT": "HELLÖ SÄP"}) 360 | assert.Equal(t, "HELLÖ SÄP", r["REQUTEXT"]) 361 | c.Close() 362 | } 363 | 364 | func TestInvalidParameterFunctionCall(t *testing.T) { 365 | fmt.Println("STFC: Invalid RFM parameter") 366 | c, err := ConnectionFromParams(abapSystem()) 367 | assert.Nil(t, err) 368 | r, err := c.Call("STFC_CONNECTION", map[string]interface{}{"XXX": "wrongParameter"}) 369 | assert.Nil(t, r) 370 | assert.NotNil(t, err) 371 | assert.Equal(t, "Could not get the parameter description for \"XXX\"", err.(*RfcError).Description) 372 | assert.Equal(t, "field 'XXX' not found", err.(*RfcError).ErrorInfo.Message) 373 | assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Code) 374 | assert.Equal(t, "RFC_INVALID_PARAMETER", err.(*RfcError).ErrorInfo.Key) 375 | c.Close() 376 | } 377 | 378 | // 379 | // Error test 380 | // 381 | 382 | func TestErrorFunctionCall(t *testing.T) { 383 | fmt.Println("Error: ABAP message") 384 | c, err := ConnectionFromParams(abapSystem()) 385 | assert.Nil(t, err) 386 | 387 | r, err := c.Call("RFC_RAISE_ERROR", map[string]interface{}{"MESSAGETYPE": "A"}) 388 | assert.Nil(t, r) 389 | assert.NotNil(t, err) 390 | assert.Equal(t, "Could not invoke function \"RFC_RAISE_ERROR\"", err.(*RfcError).Description) 391 | assert.Equal(t, "Function not supported", err.(*RfcError).ErrorInfo.Message) 392 | assert.Equal(t, "RFC_ABAP_MESSAGE", err.(*RfcError).ErrorInfo.Code) 393 | assert.Equal(t, "Function not supported", err.(*RfcError).ErrorInfo.Key) 394 | assert.Equal(t, "SR", err.(*RfcError).ErrorInfo.AbapMsgClass) 395 | assert.Equal(t, "A", err.(*RfcError).ErrorInfo.AbapMsgType) 396 | assert.Equal(t, "006", err.(*RfcError).ErrorInfo.AbapMsgNumber) 397 | assert.Equal(t, "STRING", err.(*RfcError).ErrorInfo.AbapMsgV1) 398 | c.Close() 399 | } 400 | 401 | func abapSystem() ConnectionParameters { 402 | return ConnectionParameters{ 403 | "user": "demo", 404 | "passwd": "welcome", 405 | "ashost": "10.68.110.51", 406 | "sysnr": "00", 407 | "client": "620", 408 | "lang": "EN", 409 | } 410 | } 411 | 412 | // 413 | // Datatypes 414 | // 415 | 416 | func TestUtcLong(t *testing.T) { 417 | fmt.Println("Datatypes: UTCLONG min, max, initial") 418 | c, err := ConnectionFromDest("QM7") 419 | assert.Nil(t, err) 420 | 421 | utctest := testutils.RFC_MATH["UTCLONG"].(map[string]string)["MIN"] 422 | r, err := c.Call("ZDATATYPES", map[string]interface{}{"IV_UTCLONG": utctest}) 423 | assert.Nil(t, err) 424 | assert.Equal(t, utctest, reflect.ValueOf(r["EV_UTCLONG"]).String()) 425 | 426 | utctest = testutils.RFC_MATH["UTCLONG"].(map[string]string)["MAX"] 427 | r, err = c.Call("ZDATATYPES", map[string]interface{}{"IV_UTCLONG": utctest}) 428 | assert.Nil(t, err) 429 | assert.Equal(t, utctest, reflect.ValueOf(r["EV_UTCLONG"]).String()) 430 | 431 | utctest = testutils.RFC_MATH["UTCLONG"].(map[string]string)["INITIAL"] 432 | r, err = c.Call("ZDATATYPES", map[string]interface{}{"IV_UTCLONG": utctest}) 433 | assert.Nil(t, err) 434 | assert.Equal(t, utctest, reflect.ValueOf(r["EV_UTCLONG"]).String()) 435 | 436 | c.Close() 437 | } 438 | 439 | func TestIntMaxPositive(t *testing.T) { 440 | fmt.Println("Datatypes: Integers max positive") 441 | c, err := ConnectionFromDest("MME") 442 | assert.Nil(t, err) 443 | 444 | rfcInt1 := testutils.RFC_MATH["RFC_INT1"].(map[string]uint8) 445 | rfcInt2 := testutils.RFC_MATH["RFC_INT2"].(map[string]int16) 446 | rfcInt4 := testutils.RFC_MATH["RFC_INT4"].(map[string]int32) 447 | 448 | importStruct := map[string]interface{}{ 449 | "RFCINT1": rfcInt1["MAX"] - 1, 450 | "RFCINT2": rfcInt2["MAX"] - 1, 451 | "RFCINT4": rfcInt4["MAX"] - 1, 452 | } 453 | 454 | params := map[string]interface{}{ 455 | "IMPORTSTRUCT": importStruct, 456 | "RFCTABLE": []interface{}{importStruct}, 457 | } 458 | r, err := c.Call("STFC_STRUCTURE", params) 459 | assert.Nil(t, err) 460 | assert.NotNil(t, r) 461 | 462 | echoStruct := r["ECHOSTRUCT"].(map[string]interface{}) 463 | rfcTable_0 := r["RFCTABLE"].([]interface{})[0].(map[string]interface{}) 464 | rfcTable_1 := r["RFCTABLE"].([]interface{})[1].(map[string]interface{}) 465 | 466 | assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"]) 467 | assert.Equal(t, importStruct["RFCINT1"], rfcTable_0["RFCINT1"]) 468 | assert.Equal(t, reflect.ValueOf(importStruct["RFCINT1"]).Uint()+1, reflect.ValueOf(rfcTable_1["RFCINT1"]).Uint()) 469 | 470 | assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"]) 471 | assert.Equal(t, importStruct["RFCINT2"], rfcTable_0["RFCINT2"]) 472 | assert.Equal(t, reflect.ValueOf(importStruct["RFCINT2"]).Int()+1, reflect.ValueOf(rfcTable_1["RFCINT2"]).Int()) 473 | 474 | assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"]) 475 | assert.Equal(t, importStruct["RFCINT4"], rfcTable_0["RFCINT4"]) 476 | assert.Equal(t, reflect.ValueOf(importStruct["RFCINT4"]).Int()+1, reflect.ValueOf(rfcTable_1["RFCINT4"]).Int()) 477 | 478 | c.Close() 479 | } 480 | 481 | func TestIntMaxNegative(t *testing.T) { 482 | fmt.Println("Datatypes: Integers max negative") 483 | c, err := ConnectionFromDest("MME") 484 | assert.Nil(t, err) 485 | 486 | rfcInt1 := testutils.RFC_MATH["RFC_INT1"].(map[string]uint8) 487 | rfcInt2 := testutils.RFC_MATH["RFC_INT2"].(map[string]int16) 488 | rfcInt4 := testutils.RFC_MATH["RFC_INT4"].(map[string]int32) 489 | 490 | importStruct := map[string]interface{}{ 491 | "RFCINT1": rfcInt1["MIN"], 492 | "RFCINT2": rfcInt2["MIN"], 493 | "RFCINT4": rfcInt4["MIN"], 494 | } 495 | 496 | params := map[string]interface{}{ 497 | "IMPORTSTRUCT": importStruct, 498 | "RFCTABLE": []interface{}{importStruct}, 499 | } 500 | r, err := c.Call("STFC_STRUCTURE", params) 501 | assert.Nil(t, err) 502 | assert.NotNil(t, r) 503 | 504 | echoStruct := r["ECHOSTRUCT"].(map[string]interface{}) 505 | rfcTable_0 := r["RFCTABLE"].([]interface{})[0].(map[string]interface{}) 506 | rfcTable_1 := r["RFCTABLE"].([]interface{})[1].(map[string]interface{}) 507 | 508 | assert.Equal(t, importStruct["RFCINT1"], echoStruct["RFCINT1"]) 509 | assert.Equal(t, importStruct["RFCINT1"], rfcTable_0["RFCINT1"]) 510 | assert.Equal(t, reflect.ValueOf(importStruct["RFCINT1"]).Uint()+1, reflect.ValueOf(rfcTable_1["RFCINT1"]).Uint()) 511 | 512 | assert.Equal(t, importStruct["RFCINT2"], echoStruct["RFCINT2"]) 513 | assert.Equal(t, importStruct["RFCINT2"], rfcTable_0["RFCINT2"]) 514 | assert.Equal(t, reflect.ValueOf(importStruct["RFCINT2"]).Int()+1, reflect.ValueOf(rfcTable_1["RFCINT2"]).Int()) 515 | 516 | assert.Equal(t, importStruct["RFCINT4"], echoStruct["RFCINT4"]) 517 | assert.Equal(t, importStruct["RFCINT4"], rfcTable_0["RFCINT4"]) 518 | assert.Equal(t, reflect.ValueOf(importStruct["RFCINT4"]).Int()+1, reflect.ValueOf(rfcTable_1["RFCINT4"]).Int()) 519 | 520 | c.Close() 521 | } 522 | 523 | func TestFloatMinMaxPositive(t *testing.T) { 524 | fmt.Println("Datatypes: Positive minimum and maximum: FLOAT, DECF16, DECF34") 525 | c, err := ConnectionFromDest("MME") 526 | assert.Nil(t, err) 527 | 528 | mathFloat := testutils.RFC_MATH["FLOAT"].(map[string]interface{}) 529 | mathDecf16 := testutils.RFC_MATH["DECF16"].(map[string]interface{}) 530 | mathDecf34 := testutils.RFC_MATH["DECF34"].(map[string]interface{}) 531 | 532 | is_input := map[string]string{ 533 | "ZFLTP_MIN": mathFloat["POS"].(map[string]string)["MIN"], 534 | "ZFLTP_MAX": mathFloat["POS"].(map[string]string)["MAX"], 535 | "ZDECF16_MIN": mathDecf16["POS"].(map[string]string)["MIN"], 536 | "ZDECF16_MAX": mathDecf16["POS"].(map[string]string)["MAX"], 537 | "ZDECF34_MIN": mathDecf34["POS"].(map[string]string)["MIN"], 538 | "ZDECF34_MAX": mathDecf34["POS"].(map[string]string)["MAX"], 539 | } 540 | 541 | params := map[string]interface{}{ 542 | "IS_INPUT": is_input, 543 | } 544 | r, err := c.Call("/COE/RBP_FE_DATATYPES", params) 545 | assert.Nil(t, err) 546 | assert.NotNil(t, r) 547 | 548 | // Float 549 | f, _ := strconv.ParseFloat(is_input["ZFLTP_MIN"], 64) 550 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZFLTP_MIN"], f) 551 | f, _ = strconv.ParseFloat(is_input["ZFLTP_MAX"], 64) 552 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZFLTP_MAX"], f) 553 | 554 | // Decf16 555 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZDECF16_MIN"], is_input["ZDECF16_MIN"]) 556 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZDECF16_MAX"], is_input["ZDECF16_MAX"]) 557 | 558 | // Decf34 559 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZDECF34_MIN"], is_input["ZDECF34_MIN"]) 560 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZDECF34_MAX"], is_input["ZDECF34_MAX"]) 561 | 562 | c.Close() 563 | } 564 | 565 | func TestFloatMinMaxNegative(t *testing.T) { 566 | fmt.Println("Datatypes: Negative minimum and maximum: FLOAT, DECF16, DECF34") 567 | c, err := ConnectionFromDest("MME") 568 | assert.Nil(t, err) 569 | 570 | mathFloat := testutils.RFC_MATH["FLOAT"].(map[string]interface{}) 571 | mathDecf16 := testutils.RFC_MATH["DECF16"].(map[string]interface{}) 572 | mathDecf34 := testutils.RFC_MATH["DECF34"].(map[string]interface{}) 573 | 574 | is_input := map[string]string{ 575 | "ZFLTP_MIN": mathFloat["NEG"].(map[string]string)["MIN"], 576 | "ZFLTP_MAX": mathFloat["NEG"].(map[string]string)["MAX"], 577 | "ZDECF16_MIN": mathDecf16["NEG"].(map[string]string)["MIN"], 578 | "ZDECF16_MAX": mathDecf16["NEG"].(map[string]string)["MAX"], 579 | "ZDECF34_MIN": mathDecf34["NEG"].(map[string]string)["MIN"], 580 | "ZDECF34_MAX": mathDecf34["NEG"].(map[string]string)["MAX"], 581 | } 582 | 583 | params := map[string]interface{}{ 584 | "IS_INPUT": is_input, 585 | } 586 | r, err := c.Call("/COE/RBP_FE_DATATYPES", params) 587 | assert.Nil(t, err) 588 | assert.NotNil(t, r) 589 | 590 | // Float 591 | f, _ := strconv.ParseFloat(is_input["ZFLTP_MIN"], 64) 592 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZFLTP_MIN"], f) 593 | f, _ = strconv.ParseFloat(is_input["ZFLTP_MAX"], 64) 594 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZFLTP_MAX"], f) 595 | 596 | // Decf16 597 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZDECF16_MIN"], is_input["ZDECF16_MIN"]) 598 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZDECF16_MAX"], is_input["ZDECF16_MAX"]) 599 | 600 | // Decf34 601 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZDECF34_MIN"], is_input["ZDECF34_MIN"]) 602 | assert.Equal(t, r["ES_OUTPUT"].(map[string]interface{})["ZDECF34_MAX"], is_input["ZDECF34_MAX"]) 603 | 604 | c.Close() 605 | } 606 | 607 | func TestRAW_and_BYTE_acceptBuffer(t *testing.T) { 608 | fmt.Println("Datatypes: RAW/BYTE/XSTRING accepts Buffer") 609 | 610 | bytesIn1 := testutils.XBytes(17) 611 | bytesIn2 := testutils.XBytes(2048) 612 | is_input := map[string]interface{}{ 613 | "ZRAW": bytesIn1, 614 | "ZRAWSTRING": bytesIn2, 615 | } 616 | params := map[string]interface{}{ 617 | "IS_INPUT": is_input, 618 | } 619 | c, err := ConnectionFromDest("MME") 620 | assert.Nil(t, err) 621 | 622 | r, err := c.Call("/COE/RBP_FE_DATATYPES", params) 623 | assert.Nil(t, err) 624 | assert.Equal(t, bytesIn1, r["ES_OUTPUT"].(map[string]interface{})["ZRAW"]) 625 | assert.Equal(t, bytesIn2, r["ES_OUTPUT"].(map[string]interface{})["ZRAWSTRING"]) 626 | c.Close() 627 | } 628 | 629 | func TestNonArrayForArrayParam(t *testing.T) { 630 | fmt.Println("Datatypes: Non-array passed to TABLE parameter") 631 | c, err := ConnectionFromDest("MME") 632 | assert.Nil(t, err) 633 | 634 | params := map[string]interface{}{ 635 | "QUERY_TABLE": "MARA", 636 | "OPTIONS": "A string instead of an array", 637 | } 638 | _, err = c.Call("RFC_READ_TABLE", params) 639 | assert.Equal(t, "GO string passed to ABAP TABLE parameter, expected GO array", err.(*GoRfcError).Description) 640 | c.Close() 641 | } 642 | -------------------------------------------------------------------------------- /gorfc/sapnwrfc.ini: -------------------------------------------------------------------------------- 1 | DEST=MME 2 | USER=demo 3 | PASSWD=welcome 4 | ASHOST=10.68.110.51 5 | SYSNR=00 6 | CLIENT=620 7 | LANG=EN 8 | 9 | DEST=QM7 10 | USER=NWRFCTEST 11 | PASSWD=Welcome1 12 | ASHOST=ldciqm7.wdf.sap.corp 13 | SYSNR=20 14 | GROUP=PUBLIC 15 | CLIENT=002 16 | LANG=EN -------------------------------------------------------------------------------- /gorfc/testutils/testutils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import "crypto/rand" 4 | 5 | var RFC_MATH = map[string]interface{}{ 6 | 7 | "RFC_INT1": map[string]uint8{ 8 | "MIN": 0, 9 | "MAX": 255, 10 | }, 11 | "RFC_INT2": map[string]int16{ 12 | "MIN": -32768, 13 | "MAX": 32767, 14 | }, 15 | "RFC_INT4": map[string]int32{ 16 | "MIN": -2147483648, 17 | "MAX": 2147483647, 18 | }, 19 | "RFC_INT8": map[string]int64{ 20 | "MIN": -9223372036854775808, 21 | "MAX": 9223372036854775807, 22 | }, 23 | "FLOAT": map[string]interface{}{ 24 | "NEG": map[string]string{ 25 | "MIN": "-2.2250738585072014E-308", 26 | "MAX": "-1.7976931348623157E+308", 27 | }, 28 | "POS": map[string]string{ 29 | "MIN": "2.2250738585072014E-308", 30 | "MAX": "1.7976931348623157E+308", 31 | }, 32 | }, 33 | "DECF16": map[string]interface{}{ 34 | "NEG": map[string]string{ 35 | "MIN": "-1E-383", 36 | "MAX": "-9.999999999999999E+384", 37 | }, 38 | "POS": map[string]string{ 39 | "MIN": "1E-383", 40 | "MAX": "9.999999999999999E+384", 41 | }, 42 | }, 43 | "DECF34": map[string]interface{}{ 44 | "NEG": map[string]string{ 45 | "MIN": "-1E-6143", 46 | "MAX": "-9.999999999999999999999999999999999E+6144", 47 | }, 48 | "POS": map[string]string{ 49 | "MIN": "1E-6143", 50 | "MAX": "9.999999999999999999999999999999999E+6144", 51 | }, 52 | }, 53 | "DATE": map[string]string{ 54 | "MIN": "00010101", 55 | "MAX": "99991231", 56 | }, 57 | "TIME": map[string]string{ 58 | "MIN": "000000", 59 | "MAX": "235959", 60 | }, 61 | "UTCLONG": map[string]string{ 62 | "MIN": "0001-01-01T00:00:00.0000000", 63 | "MAX": "9999-12-31T23:59:59.9999999", 64 | "INITIAL": "0000-00-00T00:00:00.0000000", 65 | }, 66 | } 67 | 68 | func XBytes(length uint) []byte { 69 | var xbytes = make([]byte, length) 70 | rand.Read(xbytes) 71 | return xbytes 72 | } 73 | --------------------------------------------------------------------------------