Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ use \Ovh\Api;
use GuzzleHttp\Client;

// Instantiate a custom Guzzle HTTP client and tweak it
$client = new Client();
$client->setDefaultOption('timeout', 1);
$client->setDefaultOption('headers', ['User-Agent' => 'api_client']);
$client = new Client([
'timeout' => 1,
'headers' => ['User-Agent' => 'api_client'],
]);

// Api credentials can be retrieved from the urls specified in the "Supported endpoints" section below.
// Inject the custom HTTP client as the 5th argument of the constructor
Expand Down
80 changes: 73 additions & 7 deletions src/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,76 @@ protected function getTarget($path) : string
return $endpoint . $path;
}

/**
* @param mixed $value
*
* @return mixed
*/
private function normalizeQueryValue($value)
{
if (is_array($value)) {
foreach ($value as $key => $item) {
$value[$key] = $this->normalizeQueryValue($item);
}

return $value;
}

if ($value === false) {
return 'false';
}

if ($value === true) {
return 'true';
}

if (is_float($value) && !is_finite($value)) {
return is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
}

return $value;
}

/**
* @param array<array-key, mixed> $headers
*
* @return array<array-key, mixed>
*/
private function normalizeHeaders(array $headers): array
{
foreach ($headers as $name => $value) {
if (is_array($value)) {
if ($value === []) {
unset($headers[$name]);
continue;
}

foreach ($value as $index => $item) {
if ($item === null || is_scalar($item) || (is_object($item) && method_exists($item, '__toString'))) {
if (is_float($item) && !is_finite($item)) {
$item = is_nan($item) ? 'NAN' : ($item > 0 ? 'INF' : '-INF');
}

$value[$index] = (string)$item;
}
}

$headers[$name] = $value;
continue;
}

if ($value === null || is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) {
if (is_float($value) && !is_finite($value)) {
$value = is_nan($value) ? 'NAN' : ($value > 0 ? 'INF' : '-INF');
}

$headers[$name] = (string)$value;
}
}

return $headers;
}

/**
* This is the main method of this wrapper. It will
* sign a given query and return its result.
Expand Down Expand Up @@ -307,13 +377,8 @@ protected function rawCall($method, $path, $content = null, $is_authenticated =

$query = array_merge($query, (array)$content);

// rewrite query args to properly dump true/false parameters
foreach ($query as $key => $value) {
if ($value === false) {
$query[$key] = "false";
} elseif ($value === true) {
$query[$key] = "true";
}
$query[$key] = $this->normalizeQueryValue($value);
}

$query = \GuzzleHttp\Psr7\Query::build($query);
Expand All @@ -331,6 +396,7 @@ protected function rawCall($method, $path, $content = null, $is_authenticated =
if (!is_array($headers)) {
$headers = [];
}
$headers = $this->normalizeHeaders($headers);
$headers['Content-Type'] = 'application/json; charset=utf-8';

if ($is_authenticated) {
Expand All @@ -342,7 +408,7 @@ protected function rawCall($method, $path, $content = null, $is_authenticated =
if (!isset($this->time_delta)) {
$this->calculateTimeDelta();
}
$now = time() + $this->time_delta;
$now = (string)(time() + $this->time_delta);

$headers['X-Ovh-Timestamp'] = $now;

Expand Down
51 changes: 50 additions & 1 deletion tests/ApiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public function testClientCreation()
public function testTimeDeltaCompute()
{
$client = new MockClient(
new Response(200, [], time() - 10),
new Response(200, [], (string)(time() - 10)),
new Response(200, [], '{}'),
);

Expand Down Expand Up @@ -326,6 +326,55 @@ public function testGetBooleanQueryArgs()
$this->assertSame('https://eu.api.ovh.com/1.0/me/api/credential?dryRun=true&notDryRun=false', $req->getUri()->__toString());
}

/**
* Test GET non-finite float query args
*/
public function testGetNonFiniteFloatQueryArgs()
{
$client = new MockClient(new Response(200, [], '{}'));

$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
$api->get('/me/api/credential', ['nan' => NAN, 'inf' => INF, 'negativeInf' => -INF], null, false);

$calls = $client->calls;
$this->assertCount(1, $calls);

$req = $calls[0]['request'];
$this->assertSame('GET', $req->getMethod());
$this->assertSame('https://eu.api.ovh.com/1.0/me/api/credential?nan=NAN&inf=INF&negativeInf=-INF', $req->getUri()->__toString());
}

/**
* Test custom scalar headers
*/
public function testCustomScalarHeaders()
{
$client = new MockClient(new Response(200, [], '{}'));

$api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client);
$api->get('/me', null, [
'X-Int' => 123,
'X-Float' => 1.5,
'X-Bool-True' => true,
'X-Bool-False' => false,
'X-Null' => null,
'X-Array' => [123, true, null],
'X-Empty-Array' => [],
], false);

$calls = $client->calls;
$this->assertCount(1, $calls);

$req = $calls[0]['request'];
$this->assertSame('123', $req->getHeaderLine('X-Int'));
$this->assertSame('1.5', $req->getHeaderLine('X-Float'));
$this->assertSame('1', $req->getHeaderLine('X-Bool-True'));
$this->assertSame('', $req->getHeaderLine('X-Bool-False'));
$this->assertSame('', $req->getHeaderLine('X-Null'));
$this->assertSame(['123', '1', ''], $req->getHeader('X-Array'));
$this->assertFalse($req->hasHeader('X-Empty-Array'));
}

/**
* Test valid provided endpoint
*/
Expand Down