diff --git a/src/Client/SugarApi.php b/src/Client/SugarApi.php index 27e6f5c..1145714 100644 --- a/src/Client/SugarApi.php +++ b/src/Client/SugarApi.php @@ -14,6 +14,7 @@ use Sugarcrm\REST\Endpoint\MLPackage; use Sugarcrm\REST\Endpoint\ModuleLoader; use Sugarcrm\REST\Endpoint\Ping; +use Sugarcrm\REST\Endpoint\Rest; use Sugarcrm\REST\Endpoint\Smart; use Sugarcrm\REST\Endpoint\SugarBean; use Sugarcrm\REST\Endpoint\ModuleFilter; @@ -48,6 +49,7 @@ * @method ModuleLoader moduleLoader() - * @method MLPackage mlp(string $id = null) * @method Integrate integrate(string $module = '', string $id = '') + * @method Rest rest(string $endpoint = '') */ class SugarApi extends AbstractClient implements PlatformAwareInterface { diff --git a/src/Endpoint/Abstracts/AbstractSmartSugarEndpoint.php b/src/Endpoint/Abstracts/AbstractSmartSugarEndpoint.php index 76d46cb..e291ffb 100644 --- a/src/Endpoint/Abstracts/AbstractSmartSugarEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractSmartSugarEndpoint.php @@ -6,11 +6,13 @@ namespace Sugarcrm\REST\Endpoint\Abstracts; +use GuzzleHttp\Psr7\Request; use MRussell\REST\Endpoint\Data\ValidatedEndpointData; use MRussell\REST\Endpoint\SmartEndpoint; use MRussell\REST\Traits\PsrLoggerTrait; use Sugarcrm\REST\Endpoint\SugarEndpointInterface; use Sugarcrm\REST\Endpoint\Traits\CompileRequestTrait; +use Sugarcrm\REST\Endpoint\Traits\CustomHeadersTrait; /** * Provide a smarter interface for Endpoints, to better manage passed in data @@ -20,6 +22,12 @@ abstract class AbstractSmartSugarEndpoint extends SmartEndpoint implements Sugar { use CompileRequestTrait; use PsrLoggerTrait; + use CustomHeadersTrait; protected string $_dataInterface = ValidatedEndpointData::class; + + protected function configureRequest(Request $request, $data): Request + { + return parent::configureRequest($this->addCustomHeadersToRequest($request), $data); + } } diff --git a/src/Endpoint/Abstracts/AbstractSugarBeanCollectionEndpoint.php b/src/Endpoint/Abstracts/AbstractSugarBeanCollectionEndpoint.php index 4fdf5b9..4387cdf 100644 --- a/src/Endpoint/Abstracts/AbstractSugarBeanCollectionEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractSugarBeanCollectionEndpoint.php @@ -6,8 +6,10 @@ namespace Sugarcrm\REST\Endpoint\Abstracts; +use GuzzleHttp\Psr7\Request; use Sugarcrm\REST\Endpoint\SugarBean; use MRussell\REST\Endpoint\Abstracts\AbstractModelEndpoint; +use Sugarcrm\REST\Endpoint\Traits\CustomHeadersTrait; use Sugarcrm\REST\Endpoint\Traits\FieldsDataTrait; use Sugarcrm\REST\Endpoint\Traits\ModuleAwareTrait; @@ -22,6 +24,7 @@ abstract class AbstractSugarBeanCollectionEndpoint extends AbstractSugarCollecti { use FieldsDataTrait; use ModuleAwareTrait; + use CustomHeadersTrait; public const SUGAR_ORDERBY_DATA_PROPERTY = 'order_by'; @@ -97,6 +100,11 @@ protected function configurePayload(): mixed return $this->configureFieldsDataProps($data); } + protected function configureRequest(Request $request, $data): Request + { + return parent::configureRequest($this->addCustomHeadersToRequest($request), $data); + } + /** * Add module to url options * @inheritdoc diff --git a/src/Endpoint/Abstracts/AbstractSugarBeanEndpoint.php b/src/Endpoint/Abstracts/AbstractSugarBeanEndpoint.php index 6557f69..957610b 100644 --- a/src/Endpoint/Abstracts/AbstractSugarBeanEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractSugarBeanEndpoint.php @@ -22,6 +22,7 @@ use Sugarcrm\REST\Endpoint\Integrate; use Sugarcrm\REST\Endpoint\SugarEndpointInterface; use Sugarcrm\REST\Endpoint\Traits\CompileRequestTrait; +use Sugarcrm\REST\Endpoint\Traits\CustomHeadersTrait; use Sugarcrm\REST\Endpoint\Traits\FieldsDataTrait; use Sugarcrm\REST\Endpoint\Traits\IntegrateSyncKeyTrait; use Sugarcrm\REST\Endpoint\Traits\ModuleAwareTrait; @@ -53,6 +54,7 @@ abstract class AbstractSugarBeanEndpoint extends ModelEndpoint implements SugarE use FieldsDataTrait; use FileUploadsTrait; use IntegrateSyncKeyTrait; + use CustomHeadersTrait; public const MODEL_ACTION_VAR = 'action'; @@ -173,7 +175,7 @@ protected function configureRequest(Request $request, $data): Request $data = $this->configureFieldsDataProps($data); } - return parent::configureRequest($request, $data); + return parent::configureRequest($this->addCustomHeadersToRequest($request), $data); } /** diff --git a/src/Endpoint/Abstracts/AbstractSugarCollectionEndpoint.php b/src/Endpoint/Abstracts/AbstractSugarCollectionEndpoint.php index 810f363..6dbdff5 100644 --- a/src/Endpoint/Abstracts/AbstractSugarCollectionEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractSugarCollectionEndpoint.php @@ -6,6 +6,7 @@ namespace Sugarcrm\REST\Endpoint\Abstracts; +use GuzzleHttp\Psr7\Request; use MRussell\REST\Exception\Endpoint\InvalidRequest; use GuzzleHttp\Psr7\Response; use MRussell\REST\Endpoint\Data\AbstractEndpointData; @@ -13,6 +14,7 @@ use MRussell\REST\Traits\PsrLoggerTrait; use Sugarcrm\REST\Endpoint\SugarEndpointInterface; use Sugarcrm\REST\Endpoint\Traits\CompileRequestTrait; +use Sugarcrm\REST\Endpoint\Traits\CustomHeadersTrait; /** * Provides access to a multi-bean collection retrieved from Sugar 7 REST Api @@ -23,6 +25,7 @@ abstract class AbstractSugarCollectionEndpoint extends CollectionEndpoint implem { use CompileRequestTrait; use PsrLoggerTrait; + use CustomHeadersTrait; public const SUGAR_OFFSET_PROPERTY = 'offset'; @@ -82,6 +85,11 @@ protected function configurePayload(): mixed return $data; } + protected function configureRequest(Request $request, $data): Request + { + return parent::configureRequest($this->addCustomHeadersToRequest($request), $data); + } + /** * Get the configured offset */ diff --git a/src/Endpoint/Abstracts/AbstractSugarEndpoint.php b/src/Endpoint/Abstracts/AbstractSugarEndpoint.php index bd147cc..489a204 100644 --- a/src/Endpoint/Abstracts/AbstractSugarEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractSugarEndpoint.php @@ -6,10 +6,12 @@ namespace Sugarcrm\REST\Endpoint\Abstracts; +use GuzzleHttp\Psr7\Request; use MRussell\REST\Endpoint\Endpoint; use MRussell\REST\Traits\PsrLoggerTrait; use Sugarcrm\REST\Endpoint\SugarEndpointInterface; use Sugarcrm\REST\Endpoint\Traits\CompileRequestTrait; +use Sugarcrm\REST\Endpoint\Traits\CustomHeadersTrait; /** * Base Sugar API Endpoint for the simplest of REST functionality @@ -19,4 +21,10 @@ abstract class AbstractSugarEndpoint extends Endpoint implements SugarEndpointIn { use CompileRequestTrait; use PsrLoggerTrait; + use CustomHeadersTrait; + + protected function configureRequest(Request $request, $data): Request + { + return parent::configureRequest($this->addCustomHeadersToRequest($request), $data); + } } diff --git a/src/Endpoint/Provider/SugarEndpointProvider.php b/src/Endpoint/Provider/SugarEndpointProvider.php index ddee4be..1ce7dd0 100644 --- a/src/Endpoint/Provider/SugarEndpointProvider.php +++ b/src/Endpoint/Provider/SugarEndpointProvider.php @@ -24,6 +24,7 @@ use Sugarcrm\REST\Endpoint\Ping; use Sugarcrm\REST\Endpoint\Note; use Sugarcrm\REST\Endpoint\Generic; +use Sugarcrm\REST\Endpoint\Rest; use Sugarcrm\REST\Endpoint\Smart; /** @@ -161,5 +162,10 @@ class SugarEndpointProvider extends VersionedEndpointProvider self::ENDPOINT_CLASS => Integrate::class, self::ENDPOINT_PROPERTIES => [], ], + [ + self::ENDPOINT_NAME => 'rest', + self::ENDPOINT_CLASS => Rest::class, + self::ENDPOINT_PROPERTIES => [], + ], ]; } diff --git a/src/Endpoint/Rest.php b/src/Endpoint/Rest.php new file mode 100644 index 0000000..ccf10f3 --- /dev/null +++ b/src/Endpoint/Rest.php @@ -0,0 +1,74 @@ +rest('custom/endpoint')->get(); + * $client->rest('custom/endpoint')->post($data); + * $client->rest('custom/endpoint')->put($data); + * $client->rest('custom/endpoint')->delete(); + * $client->rest('custom/endpoint')->patch($data); + * $client->rest('custom/endpoint')->withHeaders($headers)->get(); + * $client->rest('Contacts')->setData(['fields' => 'id,first_name,last_name', 'max_num' => 1])->get(); + * etc. + */ +class Rest extends Generic +{ + protected static array $_DEFAULT_PROPERTIES = [ + self::PROPERTY_URL => '$endpoint', + self::PROPERTY_AUTH => true, + self::PROPERTY_HTTP_METHOD => "GET", + ]; + + public function get(mixed $data = null): static + { + $this->setProperty(self::PROPERTY_HTTP_METHOD, 'GET'); + if (!is_null($data)) { + $this->setData($data); + } + return $this->execute(); + } + + public function post(mixed $data = null): static + { + $this->setProperty(self::PROPERTY_HTTP_METHOD, 'POST'); + if (!is_null($data)) { + $this->setData($data); + } + return $this->execute(); + } + + public function put(mixed $data = null): static + { + $this->setProperty(self::PROPERTY_HTTP_METHOD, 'PUT'); + if (!is_null($data)) { + $this->setData($data); + } + return $this->execute(); + } + + public function delete(): static + { + $this->setProperty(self::PROPERTY_HTTP_METHOD, 'DELETE'); + return $this->execute(); + } + + public function patch(mixed $data = null): static + { + $this->setProperty(self::PROPERTY_HTTP_METHOD, 'PATCH'); + if (!is_null($data)) { + $this->setData($data); + } + return $this->execute(); + } + +} diff --git a/src/Endpoint/Traits/CustomHeadersTrait.php b/src/Endpoint/Traits/CustomHeadersTrait.php new file mode 100644 index 0000000..fac6a7c --- /dev/null +++ b/src/Endpoint/Traits/CustomHeadersTrait.php @@ -0,0 +1,47 @@ +customHeaders[$normalized] = [$name, $value]; + return $this; + } + + /** + * Remove a custom header from the request + * @param string $name + * @return $this + */ + public function removeCustomHeader(string $name): static + { + $normalized = strtolower($name); + if (isset($this->customHeaders[$normalized])) { + unset($this->customHeaders[$normalized]); + } + return $this; + } + + protected function addCustomHeadersToRequest(Request $request): Request + { + if (!empty($this->customHeaders)) { + foreach ($this->customHeaders as $headerNormalized => $values) { + $request = $request->withHeader($values[0], $values[1]); + } + } + return $request; + } +} diff --git a/tests/Endpoint/RestTest.php b/tests/Endpoint/RestTest.php new file mode 100644 index 0000000..4dac673 --- /dev/null +++ b/tests/Endpoint/RestTest.php @@ -0,0 +1,125 @@ +client = new Client(); + parent::setUp(); + } + + public function testGetRequestToCustomEndpoint() + { + $this->client->mockResponses->append(new Response(200, [], 'OK')); + $rest = $this->client->rest('custom/endpoint'); + $rest->setBaseUrl('http://localhost/rest/v11'); + $rest->get(['foo' => 'bar']); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('http://localhost/rest/v11/custom/endpoint?foo=bar', (string) $request->getUri()); + $this->assertStringContainsString('foo=bar', (string) $request->getUri()->getQuery()); + } + + public function testPostRequestWithData() + { + $this->client->mockResponses->append(new Response(200, [], 'OK')); + $rest = new Rest([], ['custom/endpoint']); + $rest->setClient($this->client); + $rest->setBaseUrl('http://localhost/rest/v11'); + $data = ['foo' => 'bar', 'baz' => 'qux']; + $rest->post($data); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('POST', $request->getMethod()); + $this->assertEquals('http://localhost/rest/v11/custom/endpoint', (string) $request->getUri()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertEquals($data, $body); + } + + public function testPutRequestWithData() + { + $this->client->mockResponses->append(new Response(200, [], 'OK')); + $rest = new Rest([], ['custom/endpoint']); + $rest->setClient($this->client); + $rest->setBaseUrl('http://localhost/rest/v11'); + $data = ['foo' => 'bar']; + $rest->put($data); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('PUT', $request->getMethod()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertEquals($data, $body); + } + + public function testPatchRequestWithData() + { + $this->client->mockResponses->append(new Response(200, [], 'OK')); + $rest = new Rest([], ['custom/endpoint']); + $rest->setClient($this->client); + $rest->setBaseUrl('http://localhost/rest/v11'); + $data = ['foo' => 'bar']; + $rest->patch($data); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('PATCH', $request->getMethod()); + $body = json_decode($request->getBody()->getContents(), true); + $this->assertEquals($data, $body); + } + + public function testDeleteRequest() + { + $this->client->mockResponses->append(new Response(200, [], 'OK')); + $rest = $this->client->rest('custom/endpoint'); + $rest->setBaseUrl('http://localhost/rest/v11'); + $rest->delete(); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertEquals('DELETE', $request->getMethod()); + $this->assertEquals('http://localhost/rest/v11/custom/endpoint', (string) $request->getUri()); + } + + public function testAddCustomHeader() + { + $this->client->mockResponses->append(new Response(200, [], 'OK')); + $rest = $this->client->rest('custom/endpoint'); + $rest->setBaseUrl('http://localhost/rest/v11'); + $rest->addCustomHeader('X-Test-Header', 'value1'); + $rest->get(); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertTrue($request->hasHeader('X-Test-Header')); + $this->assertEquals(['value1'], $request->getHeader('X-Test-Header')); + } + + public function testRemoveCustomHeader() + { + $this->client->mockResponses->append(new Response(200, [], 'OK')); + $rest = $this->client->rest('custom/endpoint'); + $rest->setBaseUrl('http://localhost/rest/v11'); + $rest->addCustomHeader('X-Test-Header', 'value1'); + $rest->removeCustomHeader('X-Test-Header'); + $rest->get(); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertFalse($request->hasHeader('X-Test-Header')); + } + + public function testMultipleCustomHeaders() + { + $this->client->mockResponses->append(new Response(200, [], 'OK')); + $rest = $this->client->rest('custom/endpoint'); + $rest->setBaseUrl('http://localhost/rest/v11'); + $rest->addCustomHeader('X-Test-Header', 'value1'); + $rest->addCustomHeader('Another-Header', 'value2'); + $rest->get(); + $request = $this->client->mockResponses->getLastRequest(); + $this->assertTrue($request->hasHeader('X-Test-Header')); + $this->assertEquals(['value1'], $request->getHeader('X-Test-Header')); + $this->assertTrue($request->hasHeader('Another-Header')); + $this->assertEquals(['value2'], $request->getHeader('Another-Header')); + } +}