├── README.md ├── RestRequest.pas ├── SimpleRestClient.groupproj ├── SimpleRestRequestSample.dpr ├── SimpleRestRequestSample.dproj └── test ├── SimpleRestRequestTests.dpr ├── SimpleRestRequestTests.dproj └── TestRestHttpWrapper.pas /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | SimpleRestClient is a very simple, fluent wrapper around the Indy IdHttp client to make it easy to write RESTful clients. This client was built in Delphi XE2, but might well work in later versions. 3 | 4 | ## Usage 5 | This very simple console application creates a RESTful PUT request to add a fictional todo. 6 | 7 | ```delphi 8 | program SimpleRestRequestSample; 9 | 10 | {$APPTYPE CONSOLE} 11 | 12 | {$R *.res} 13 | 14 | uses 15 | System.SysUtils, 16 | Classes, 17 | RestRequest in 'RestRequest.pas'; 18 | 19 | var RestReq: TRestRequest; 20 | RestResp: THttpResponse; 21 | putParams: TStringList; 22 | begin 23 | try 24 | try 25 | putParams := TStringList.Create(); 26 | putParams.Add('title=Buy milk'); 27 | putParams.Add('due-date=01/01/2013 00:00:00'); 28 | RestReq := TRestRequest.Create().Domain('localhost').Path('todo').WithCredentials('test', 'test'); 29 | RestResp := RestReq.Put(putParams); 30 | if RestResp.ResponseCode = 201 then WriteLn('Your todo was added!'); 31 | finally 32 | RestReq.Free; 33 | end; 34 | except 35 | on E: Exception do 36 | Writeln(E.ClassName, ': ', E.Message); 37 | end; 38 | end. 39 | ``` 40 | 41 | ## Limitations and improvements 42 | SimpleRestClient was thrown together pretty quickly to fill a need that I had and has limitations in areas that I didn't need or have time to do. 43 | * Test coverage - There is some test coverage but I didn't have time to mock out the Indy IdHttp client. 44 | * Patch support - The indy client doesn't support the PATCH request at present. 45 | * Accept headers - I'd like a better solution (read: more Delphi like) to Accept headers. 46 | 47 | ## Licensing 48 | Copyright (c) 2015, Jamie Ingilby 49 | All rights reserved. 50 | 51 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 52 | 53 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 54 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 55 | Neither the name of SimpleRestClient for Delphi nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 56 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /RestRequest.pas: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamiei/SimpleRestClient/f9ceb8c48ac3f58fabe95cbfff43950770611a39/RestRequest.pas -------------------------------------------------------------------------------- /SimpleRestClient.groupproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {19213358-C3A3-4CA3-BBD2-6E7F92BDD92B} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Default.Personality.12 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /SimpleRestRequestSample.dpr: -------------------------------------------------------------------------------- 1 | program SimpleRestRequestSample; 2 | 3 | {$APPTYPE CONSOLE} 4 | 5 | {$R *.res} 6 | 7 | uses 8 | System.SysUtils, 9 | Classes, 10 | RestRequest in 'RestRequest.pas'; 11 | 12 | var RestReq: TRestRequest; 13 | RestResp: THttpResponse; 14 | begin 15 | try 16 | RestReq := nil; 17 | try 18 | RestReq := TRestRequest.Create().Domain('jsonplaceholder.typicode.com').Path('todos').Path('1'); 19 | RestResp := RestReq.Get(); 20 | if RestResp.ResponseCode = 200 then WriteLn('Your todo was added!') else WriteLn('Failed to add your todo.'); 21 | WriteLn(RestResp.ResponseStr); 22 | finally 23 | RestReq.Free; 24 | end; 25 | except 26 | on E: Exception do 27 | Writeln(E.ClassName, ': ', E.Message); 28 | end; 29 | end. 30 | -------------------------------------------------------------------------------- /SimpleRestRequestSample.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {E501D82E-B7B1-44D7-908A-52E367968669} 4 | 13.4 5 | None 6 | SimpleRestRequestSample.dpr 7 | True 8 | Debug 9 | Win32 10 | 1 11 | Console 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Cfg_1 34 | true 35 | true 36 | 37 | 38 | true 39 | Base 40 | true 41 | 42 | 43 | System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) 44 | bindcompfmx;fmx;rtl;dbrtl;IndySystem;DbxClientDriver;bindcomp;inetdb;DBXInterBaseDriver;xmlrtl;DbxCommonDriver;IndyProtocols;DBXMySQLDriver;dbxcds;soaprtl;bindengine;CustomIPTransport;dsnap;fmxase;IndyCore;inet;fmxobj;inetdbxpress;fmxdae;IPIndyImpl;dbexpress;$(DCC_UsePackage) 45 | .\$(Platform)\$(Config) 46 | .\$(Platform)\$(Config) 47 | false 48 | false 49 | false 50 | false 51 | false 52 | 53 | 54 | vclimg;vclactnband;vcldb;bindcompvcl;vcldsnap;vclie;vcltouch;websnap;VclSmp;vcl;dsnapcon;vclx;webdsnap;$(DCC_UsePackage) 55 | 56 | 57 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 58 | 1033 59 | vcldbx;TeeDB;vclib;inetdbbde;Tee;svnui;ibxpress;vclimg;fmi;intrawebdb_120_160;vclactnband;FMXTee;TeeUI;vcldb;bindcompvcl;vcldsnap;vclie;vcltouch;Intraweb_120_160;websnap;vclribbon;VclSmp;vcl;CloudService;CodeSiteExpressPkg;FmxTeeUI;dsnapcon;vclx;webdsnap;svn;bdertl;adortl;$(DCC_UsePackage) 60 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 61 | 62 | 63 | DEBUG;$(DCC_Define) 64 | false 65 | true 66 | true 67 | true 68 | 69 | 70 | false 71 | 72 | 73 | false 74 | RELEASE;$(DCC_Define) 75 | 0 76 | false 77 | 78 | 79 | 80 | MainSource 81 | 82 | 83 | 84 | Cfg_2 85 | Base 86 | 87 | 88 | Base 89 | 90 | 91 | Cfg_1 92 | Base 93 | 94 | 95 | 96 | Delphi.Personality.12 97 | 98 | 99 | 100 | 101 | False 102 | False 103 | 1 104 | 0 105 | 0 106 | 0 107 | False 108 | False 109 | False 110 | False 111 | False 112 | 2057 113 | 1252 114 | 115 | 116 | 117 | 118 | 1.0.0.0 119 | 120 | 121 | 122 | 123 | 124 | 1.0.0.0 125 | 126 | 127 | 128 | SimpleRestRequestSample.dpr 129 | 130 | 131 | 132 | 133 | False 134 | False 135 | True 136 | 137 | 138 | 12 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /test/SimpleRestRequestTests.dpr: -------------------------------------------------------------------------------- 1 | program SimpleRestRequestTests; 2 | { 3 | 4 | Delphi DUnit Test Project 5 | ------------------------- 6 | This project contains the DUnit test framework and the GUI/Console test runners. 7 | Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options 8 | to use the console test runner. Otherwise the GUI test runner will be used by 9 | default. 10 | 11 | } 12 | 13 | {$IFDEF CONSOLE_TESTRUNNER} 14 | {$APPTYPE CONSOLE} 15 | {$ENDIF} 16 | 17 | uses 18 | TestRestHttpWrapper in 'TestRestHttpWrapper.pas', 19 | RestRequest in '..\RestRequest.pas', 20 | DUnitTestRunner; 21 | 22 | {R *.RES} 23 | 24 | begin 25 | DUnitTestRunner.RunRegisteredTests; 26 | end. 27 | 28 | -------------------------------------------------------------------------------- /test/SimpleRestRequestTests.dproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | {371C787F-0816-46B0-8872-975520E015F1} 4 | 14.2 5 | None 6 | True 7 | Debug 8 | Win32 9 | 1 10 | Console 11 | SimpleRestRequestTests.dpr 12 | 13 | 14 | true 15 | 16 | 17 | true 18 | Base 19 | true 20 | 21 | 22 | true 23 | Base 24 | true 25 | 26 | 27 | true 28 | Base 29 | true 30 | 31 | 32 | true 33 | Base 34 | true 35 | 36 | 37 | true 38 | Cfg_1 39 | true 40 | true 41 | 42 | 43 | true 44 | Base 45 | true 46 | 47 | 48 | System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) 49 | $(BDS)\Source\DUnit\src;$(DCC_UnitSearchPath) 50 | _CONSOLE_TESTRUNNER;$(DCC_Define) 51 | . 52 | .\$(Platform)\$(Config) 53 | false 54 | false 55 | false 56 | false 57 | false 58 | 59 | 60 | bindcompfmx;DBXSqliteDriver;fmx;rtl;dbrtl;IndySystem;DbxClientDriver;bindcomp;inetdb;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DBXOdbcDriver;DataSnapServer;DataSnapProviderClient;xmlrtl;DbxCommonDriver;DBXSybaseASEDriver;vclimg;IndyProtocols;DBXMySQLDriver;dbxcds;bindengine;vclactnband;vcldb;soaprtl;vcldsnap;bindcompvcl;vclie;vcltouch;DBXDb2Driver;websnap;DBXOracleDriver;CustomIPTransport;VclSmp;dsnap;IndyIPServer;DBXInformixDriver;IndyCore;vcl;fmxase;IndyIPCommon;DBXMSSQLDriver;dsnapcon;DBXFirebirdDriver;inet;fmxobj;vclx;inetdbxpress;webdsnap;DBXSybaseASADriver;fmxdae;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 61 | 62 | 63 | bindcompfmx;DBXSqliteDriver;fmx;rtl;dbrtl;IndySystem;DbxClientDriver;bindcomp;inetdb;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapServer;DataSnapProviderClient;xmlrtl;DbxCommonDriver;IndyProtocols;DBXMySQLDriver;dbxcds;bindengine;soaprtl;DBXOracleDriver;CustomIPTransport;dsnap;IndyIPServer;DBXInformixDriver;IndyCore;fmxase;IndyIPCommon;DBXFirebirdDriver;inet;fmxobj;inetdbxpress;DBXSybaseASADriver;fmxdae;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) 64 | 65 | 66 | Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) 67 | 1033 68 | bindcompfmx;DBXSqliteDriver;vcldbx;fmx;rtl;dbrtl;IndySystem;DbxClientDriver;bindcomp;inetdb;vclib;inetdbbde;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DBXOdbcDriver;DataSnapServer;DataSnapProviderClient;xmlrtl;svnui;ibxpress;DbxCommonDriver;DBXSybaseASEDriver;vclimg;IndyProtocols;DBXMySQLDriver;dbxcds;bindengine;vclactnband;vcldb;soaprtl;vcldsnap;bindcompvcl;vclie;vcltouch;DBXDb2Driver;websnap;DBXOracleDriver;CustomIPTransport;vclribbon;VclSmp;dsnap;IndyIPServer;DBXInformixDriver;IndyCore;vcl;fmxase;DataSnapConnectors;IndyIPCommon;CloudService;DBXMSSQLDriver;dsnapcon;DBXFirebirdDriver;inet;fmxobj;vclx;inetdbxpress;webdsnap;svn;DBXSybaseASADriver;fmxdae;bdertl;dbexpress;DataSnapIndy10ServerTransport;adortl;IndyIPClient;$(DCC_UsePackage) 69 | CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= 70 | 71 | 72 | DEBUG;$(DCC_Define) 73 | false 74 | true 75 | true 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | RELEASE;$(DCC_Define) 84 | 0 85 | false 86 | 87 | 88 | 89 | MainSource 90 | 91 | 92 | 93 | 94 | Cfg_2 95 | Base 96 | 97 | 98 | Base 99 | 100 | 101 | Cfg_1 102 | Base 103 | 104 | 105 | 106 | Delphi.Personality.12 107 | 108 | 109 | 110 | 111 | False 112 | False 113 | 1 114 | 0 115 | 0 116 | 0 117 | False 118 | False 119 | False 120 | False 121 | False 122 | 2057 123 | 1252 124 | 125 | 126 | 127 | 128 | 1.0.0.0 129 | 130 | 131 | 132 | 133 | 134 | 1.0.0.0 135 | 136 | 137 | 138 | SimpleRestRequestTests.dpr 139 | 140 | 141 | 142 | 143 | False 144 | False 145 | True 146 | 147 | 148 | DUnit / Delphi Win32 149 | GUI 150 | D:\Dev\Delphi XE2 Projects\Waurie\Waurie.dproj 151 | 152 | 153 | 12 154 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /test/TestRestHttpWrapper.pas: -------------------------------------------------------------------------------- 1 | unit TestRestHttpWrapper; 2 | { 3 | 4 | Delphi DUnit Test Case 5 | ---------------------- 6 | This unit contains a skeleton test case class generated by the Test Case Wizard. 7 | Modify the generated code to correctly setup and call the methods from the unit 8 | being tested. 9 | 10 | } 11 | 12 | interface 13 | 14 | uses 15 | TestFramework, IdHttp, Data.DBXJSON, IdAuthentication, Classes, SysUtils, 16 | RestRequest, IdMultipartFormData, IdURI; 17 | 18 | type 19 | // Test methods for class TRestRequest 20 | 21 | TestTRestRequest = class(TTestCase) 22 | strict private 23 | FRestRequest: TRestRequest; 24 | public 25 | procedure SetUp; override; 26 | procedure TearDown; override; 27 | published 28 | procedure TestDomain; 29 | procedure TestPath; 30 | procedure TestMultiPath; 31 | procedure TestSingleParam; 32 | procedure TestMultiParam; 33 | procedure TestWithCredentials; 34 | procedure TestGet; 35 | procedure TestPut; 36 | procedure TestPost; 37 | procedure TestDelete; 38 | end; 39 | 40 | implementation 41 | 42 | procedure TestTRestRequest.SetUp; 43 | begin 44 | FRestRequest := TRestRequest.Create; 45 | end; 46 | 47 | procedure TestTRestRequest.TearDown; 48 | begin 49 | FRestRequest.Free; 50 | FRestRequest := nil; 51 | end; 52 | 53 | procedure TestTRestRequest.TestDomain; 54 | var 55 | Request: TRestRequest; 56 | aDomain: string; 57 | begin 58 | aDomain := 'api.test.com'; 59 | FRestRequest := FRestRequest.Domain(aDomain); 60 | Assert(FRestRequest.FullUrl = 'http://api.test.com', 'Domain test: ' + FRestRequest.FullUrl); 61 | end; 62 | 63 | procedure TestTRestRequest.TestPath; 64 | var 65 | ReturnValue: TRestRequest; 66 | aPath: string; 67 | begin 68 | aPath := 'player'; 69 | FRestRequest := FRestRequest.Path(aPath); 70 | Assert(FRestRequest.FullUrl = 'http:///' + aPath, 'Single Path Test: ' + FRestRequest.FullUrl); 71 | end; 72 | 73 | procedure TestTRestRequest.TestSingleParam; 74 | var 75 | ReturnValue: TRestRequest; 76 | aValue: string; 77 | aKey: string; 78 | Expected: string; 79 | begin 80 | aKey := 'limit'; 81 | aValue := '20'; 82 | ReturnValue := FRestRequest.Param(aKey, aValue); 83 | Expected := 'http://?' + aKey + '=' + aValue; 84 | Assert(FRestRequest.FullUrl = Expected, 'Single Param Test: ' + FRestRequest.FullUrl + ' - Expected: ' + Expected); 85 | end; 86 | 87 | procedure TestTRestRequest.TestWithCredentials; 88 | var 89 | ReturnValue: TRestRequest; 90 | password: string; 91 | username: string; 92 | begin 93 | // TODO: Setup method call parameters 94 | ReturnValue := FRestRequest.WithCredentials(username, password); 95 | // TODO: Validate method results 96 | end; 97 | 98 | 99 | procedure TestTRestRequest.TestGet; 100 | var 101 | ReturnValue: THttpResponse; 102 | begin 103 | //ReturnValue := FRestRequest.Get; 104 | // TODO: Validate method results 105 | end; 106 | 107 | procedure TestTRestRequest.TestMultiParam; 108 | var 109 | ReturnValue: TRestRequest; 110 | aValue1, aValue2: string; 111 | aKey1, aKey2: string; 112 | Expected: string; 113 | begin 114 | // Param 1 115 | aKey1 := 'limit'; 116 | aValue1 := '20'; 117 | // Param 2 118 | aKey2 := 'type'; 119 | aValue2 := 'member'; 120 | ReturnValue := FRestRequest.Param(aKey1, aValue1).Param(aKey2, aValue2); 121 | Expected := 'http://?' + aKey1 + '=' + aValue1 + '&' + aKey2 + '=' + aValue2; 122 | Assert(FRestRequest.FullUrl = Expected, 'Single Param Test: ' + FRestRequest.FullUrl + ' - Expected: ' + Expected); 123 | 124 | end; 125 | 126 | procedure TestTRestRequest.TestMultiPath; 127 | var 128 | ReturnValue: TRestRequest; 129 | aPath, aPath2: string; 130 | begin 131 | aPath := 'player'; 132 | aPath2 := '1'; 133 | FRestRequest := FRestRequest.Path(aPath).Path(aPath2); 134 | Assert(FRestRequest.FullUrl = 'http:///' + aPath + '/' + aPath2, 'Double Path Test: ' + FRestRequest.FullUrl); 135 | end; 136 | 137 | procedure TestTRestRequest.TestPut; 138 | var 139 | ReturnValue: THttpResponse; 140 | aParams: TStringList; 141 | begin 142 | // TODO: Setup method call parameters 143 | //ReturnValue := FRestRequest.Put(aParams); 144 | // TODO: Validate method results 145 | end; 146 | 147 | procedure TestTRestRequest.TestPost; 148 | var 149 | ReturnValue: THttpResponse; 150 | aParams: TStringList; 151 | begin 152 | // TODO: Setup method call parameters 153 | //ReturnValue := FRestRequest.Post(aParams); 154 | // TODO: Validate method results 155 | end; 156 | 157 | procedure TestTRestRequest.TestDelete; 158 | var 159 | ReturnValue: THttpResponse; 160 | begin 161 | //ReturnValue := FRestRequest.Delete; 162 | // TODO: Validate method results 163 | end; 164 | 165 | initialization 166 | // Register any test cases with the test runner 167 | RegisterTest(TestTRestRequest.Suite); 168 | end. 169 | 170 | --------------------------------------------------------------------------------