Skip to content

Commit

Permalink
Merge pull request #42 from picqer/specs-downloader
Browse files Browse the repository at this point in the history
Script to download latest specs
  • Loading branch information
casperbakker authored Jan 2, 2024
2 parents 452a528 + 2c64bb1 commit 45243ed
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 77 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Bol.com Retailer API client for PHP
This is an open source PHP client for the [Bol.com Retailer API](https://api.bol.com/retailer/public/Retailer-API/v10/releasenotes.html) version 10.4.
This is an open source PHP client for the [Bol.com Retailer API](https://api.bol.com/retailer/public/Retailer-API/v10/releasenotes.html) version 10.6.

## Installation
This project can easily be installed through Composer:
Expand Down Expand Up @@ -179,10 +179,15 @@ Please follow the guidelines below if you want to contribute.
- Keep in mind that we want to support PHP 7.1 as long as possible.

## Generated Models and Client
The Client and all models are generated by the supplied [Retailer API specifications](https://api.bol.com/retailer/public/apispec/Retailer%20API%20-%20v10) (`src/OpenApi/retailer.json`) and [Shared API specification](https://api.bol.com/retailer/public/apispec/Shared%20API%20-%20v10) (`src/OpenApi/shared.json`). These specifications are merged. Generating the code ensures there are no typos, not every operation needs a test and future (minor) updates to the specifications can easily be applied. To build the classes for the latest Bol Retailer API version, replace the two specification files with the latest version first.
The Client and all models are generated by the supplied [Retailer API specifications](https://api.bol.com/retailer/public/apispec/Retailer%20API%20-%20v10) (`src/OpenApi/retailer.json`) and [Shared API specification](https://api.bol.com/retailer/public/apispec/Shared%20API%20-%20v10) (`src/OpenApi/shared.json`). These specifications are merged. Generating the code ensures there are no typos, not every operation needs a test and future (minor) updates to the specifications can easily be applied.

The generated classes contain all data required to properly map method arguments and response data to the models: the specifications are only used to generate them.

To build the classes for the latest Bol Retailer API version, let the code download the newest specs with this script:
```
composer run-script download-specs
```

### Client
The Client contains all operations specified in the specifications. The 'operationId' value is converted to camelCase and used as method name for each operation.

Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"test": "phpunit",
"check-style": "phpcs src tests",
"fix-style": "phpcbf src tests",
"download-specs": "Picqer\\BolRetailerV10\\OpenApi\\SpecsDownloader::run",
"generate-client": "Picqer\\BolRetailerV10\\OpenApi\\ClientGenerator::run",
"generate-models": "Picqer\\BolRetailerV10\\OpenApi\\ModelGenerator::run"
},
Expand Down
11 changes: 2 additions & 9 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -1577,26 +1577,19 @@ public function getInvoiceRequests(?string $shipmentId = null, ?int $page = 1, ?
/**
* Uploads an invoice associated with shipment id.
* @param string $shipmentId The id of the shipment associated with the invoice.
* @param string $invoice
* @return Model\ProcessStatus|null
* @throws Exception\ConnectException when an error occurred in the HTTP connection.
* @throws Exception\ResponseException when an unexpected response was received.
* @throws Exception\UnauthorizedException when the request was unauthorized.
* @throws Exception\RateLimitException when the throttling limit has been reached for the API user.
* @throws Exception\Exception when something unexpected went wrong.
*/
public function uploadInvoice(string $shipmentId, string $invoice): ?Model\ProcessStatus
public function uploadInvoice(string $shipmentId): ?Model\ProcessStatus
{
$url = "retailer/shipments/invoices/{$shipmentId}";
$options = [
'multipart' => [
[
'name' => 'invoice',
'contents' => \GuzzleHttp\Psr7\Utils::tryFopen($invoice, 'r'),
],
],
'produces' => 'application/vnd.retailer.v10+json',
'consumes' => 'multipart/form-data',
'consumes' => 'application/json',
];
$responseTypes = [
'202' => Model\ProcessStatus::class,
Expand Down
18 changes: 18 additions & 0 deletions src/Model/RetailerInformationResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public function getModelDefinition(): array
return [
'retailerId' => [ 'model' => null, 'enum' => null, 'array' => false ],
'displayName' => [ 'model' => null, 'enum' => null, 'array' => false ],
'companyName' => [ 'model' => null, 'enum' => null, 'array' => false ],
'vatNumber' => [ 'model' => null, 'enum' => null, 'array' => false ],
'kvkNumber' => [ 'model' => null, 'enum' => null, 'array' => false ],
'registrationDate' => [ 'model' => null, 'enum' => null, 'array' => false ],
'topRetailer' => [ 'model' => null, 'enum' => null, 'array' => false ],
'ratingMethod' => [ 'model' => null, 'enum' => Enum\RetailerInformationResponseRatingMethod::class, 'array' => false ],
Expand All @@ -37,6 +40,21 @@ public function getModelDefinition(): array
*/
public $displayName;

/**
* @var string The company name of the retailer.
*/
public $companyName;

/**
* @var string The VAT number of the retailer.
*/
public $vatNumber;

/**
* @var string The KVK number of the retailer.
*/
public $kvkNumber;

/**
* @var string A date representing the registration date for the retailer within bol.com
*/
Expand Down
6 changes: 6 additions & 0 deletions src/Model/SubscriptionRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public function getModelDefinition(): array
'resources' => [ 'model' => null, 'enum' => null, 'array' => true ],
'url' => [ 'model' => null, 'enum' => null, 'array' => false ],
'subscriptionType' => [ 'model' => null, 'enum' => Enum\SubscriptionRequestSubscriptionType::class, 'array' => false ],
'enabled' => [ 'model' => null, 'enum' => null, 'array' => false ],
];
}

Expand All @@ -40,4 +41,9 @@ public function getModelDefinition(): array
* events will be subscribed to. Be aware that certain event types are only available for specific types.
*/
public $subscriptionType;

/**
* @var bool Whether the subscription is enabled and will receive notifications or not. Defaults to true.
*/
public $enabled;
}
6 changes: 6 additions & 0 deletions src/Model/SubscriptionResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public function getModelDefinition(): array
'resources' => [ 'model' => null, 'enum' => null, 'array' => true ],
'url' => [ 'model' => null, 'enum' => null, 'array' => false ],
'subscriptionType' => [ 'model' => null, 'enum' => Enum\SubscriptionResponseSubscriptionType::class, 'array' => false ],
'enabled' => [ 'model' => null, 'enum' => null, 'array' => false ],
];
}

Expand All @@ -46,4 +47,9 @@ public function getModelDefinition(): array
* events will be subscribed to. Be aware that certain event types are only available for specific types.
*/
public $subscriptionType;

/**
* @var bool Whether the subscription is enabled and will receive notifications or not. Defaults to true.
*/
public $enabled;
}
29 changes: 14 additions & 15 deletions src/OpenApi/ClientGenerator.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?php


namespace Picqer\BolRetailerV10\OpenApi;

class ClientGenerator
Expand Down Expand Up @@ -216,7 +215,7 @@ protected function kebabCaseToCamelCase(string $name): string
$name = str_replace(' ', '-', $name);

$nameElems = explode('-', $name);
for ($i=1; $i<count($nameElems); $i++) {
for ($i = 1; $i < count($nameElems); $i++) {
$nameElems[$i] = ucfirst($nameElems[$i]);
}
return implode('', $nameElems);
Expand Down Expand Up @@ -258,8 +257,8 @@ protected function extractArguments(array $methodDefinition): array
if ($parameter['in'] == 'query' && isset($parameter['schema']['$ref'])) {
continue;
} elseif ($parameter['in'] == 'query' && isset($parameter['schema']['enum'])) {
$wrappingType = ucfirst($this->kebabCaseToCamelCase($methodDefinition['operationId'] .'-'. $parameter['name']));
$argument['php'] = 'Enum\\'.$wrappingType;
$wrappingType = ucfirst($this->kebabCaseToCamelCase($methodDefinition['operationId'] . '-' . $parameter['name']));
$argument['php'] = 'Enum\\' . $wrappingType;
$argument['doc'] = $argument['php'];
$argument['name'] = $this->kebabCaseToCamelCase($parameter['name']);
$argument['paramName'] = $parameter['name'];
Expand Down Expand Up @@ -322,24 +321,24 @@ protected function extractArguments(array $methodDefinition): array

if (isset($propSchema['type']) && $propSchema['type'] == 'array') {
$itemsType = $this->getType($propSchema['items']['$ref']);
$argument['doc'] = 'Model\\'.$itemsType.'[]';
$argument['doc'] = 'Model\\' . $itemsType . '[]';
$argument['php'] = 'array';
} elseif (isset($propSchema['type'])) {
$wrappingType = static::$paramTypeMapping[$propSchema['type']];
$argument['doc'] = $wrappingType;
$argument['php'] = $wrappingType;
} else {
$wrappingType = $this->getType($propSchema['$ref']);
$argument['doc'] = 'Model\\'.$wrappingType;
$argument['php'] = 'Model\\'.$wrappingType;
$argument['doc'] = 'Model\\' . $wrappingType;
$argument['php'] = 'Model\\' . $wrappingType;
}
$argument['property'] = $property;
$argument['name'] = $property;
$argument['wrapperPhp'] = 'Model\\'.$type;
$argument['wrapperPhp'] = 'Model\\' . $type;
}

if (!isset($argument['property'])) {
$argument['php'] = 'Model\\'.$type;
if (! isset($argument['property'])) {
$argument['php'] = 'Model\\' . $type;
$argument['doc'] = $argument['php'];
$argument['name'] = lcfirst($type);
}
Expand Down Expand Up @@ -397,7 +396,7 @@ protected function argumentValueToString($argument): string
protected function addQueryParams(array $arguments, array &$code): void
{
$amount = array_reduce($arguments, function ($amount, $argument) {
return $argument['in'] == 'query' ? $amount+1 : $amount;
return $argument['in'] == 'query' ? $amount + 1 : $amount;
});

if ($amount == 0) {
Expand Down Expand Up @@ -441,7 +440,7 @@ protected function addBodyParam(array $arguments, array &$code): void
protected function addFormData(array $arguments, array &$code): void
{
$containsFileArgument = in_array(true, array_map(
static fn (array $argument): bool => $argument['is_file'] ?? false,
static fn(array $argument): bool => $argument['is_file'] ?? false,
$arguments,
));
$formData = [];
Expand Down Expand Up @@ -524,8 +523,8 @@ protected function getReturnType(array $responses): array
if (isset($refSchema['properties'][$property]['type'], $refSchema['properties'][$property]['items']['$ref']) && $refSchema['properties'][$property]['type'] == 'array') {
return [
'doc' => 'Model\\' . $this->getType(
$refSchema['properties'][$property]['items']['$ref']
) . '[]',
$refSchema['properties'][$property]['items']['$ref']
) . '[]',
'php' => 'array',
'property' => $property
];
Expand Down Expand Up @@ -555,7 +554,7 @@ protected function getEnumName(string $name): string
// We add the first `_` for enums starting with a integer character
$prefix = is_numeric($name[0]) ? '_' : '';

return $prefix.strtoupper($name);
return $prefix . strtoupper($name);
}

protected function wrapComment(string $comment, string $linePrefix, int $maxLength = 120): string
Expand Down
26 changes: 12 additions & 14 deletions src/OpenApi/ModelGenerator.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?php


namespace Picqer\BolRetailerV10\OpenApi;

class ModelGenerator
Expand Down Expand Up @@ -50,12 +49,12 @@ public function generateEnums(): void
foreach ($this->specs['paths'] as $path => $methodsDef) {
foreach ($methodsDef as $method => $methodDef) {
foreach ($methodDef['parameters'] ?? [] as $parameterDef) {
if (!isset($parameterDef['schema']['enum']) || !array_values(array_filter($parameterDef['schema']['enum']))) {
if (! isset($parameterDef['schema']['enum']) || ! array_values(array_filter($parameterDef['schema']['enum']))) {
continue;
}

$this->generateEnum(
ucfirst($this->kebabCaseToCamelCase($methodDef['operationId'] .'-'. $parameterDef['name'])),
ucfirst($this->kebabCaseToCamelCase($methodDef['operationId'] . '-' . $parameterDef['name'])),
static::$propTypeMapping[$parameterDef['schema']['type']],
$parameterDef['schema']['enum']
);
Expand All @@ -65,12 +64,12 @@ public function generateEnums(): void

foreach ($this->specs['components']['schemas'] as $type => $modelSchema) {
foreach ($modelSchema['properties'] as $property => $propertyDef) {
if (!isset($propertyDef['enum']) || !array_values(array_filter($propertyDef['enum']))) {
if (! isset($propertyDef['enum']) || ! array_values(array_filter($propertyDef['enum']))) {
continue;
}

$this->generateEnum(
ucfirst($this->kebabCaseToCamelCase($type .'-'. $property)),
ucfirst($this->kebabCaseToCamelCase($type . '-' . $property)),
static::$propTypeMapping[$propertyDef['type']],
$propertyDef['enum']
);
Expand Down Expand Up @@ -146,7 +145,7 @@ protected function generateSchema(string $type, array $modelSchema, array &$code
$enum = 'null';
$array = 'false';

if (isset($propDefinition['type']) && !isset($propDefinition['enum'])) {
if (isset($propDefinition['type']) && ! isset($propDefinition['enum'])) {
if ($propDefinition['type'] == 'array') {
$array = 'true';
if (isset($propDefinition['items']['$ref'])) {
Expand All @@ -155,8 +154,8 @@ protected function generateSchema(string $type, array $modelSchema, array &$code
}
} elseif (isset($propDefinition['$ref'])) {
$model = $this->getType($propDefinition['$ref']) . '::class';
} elseif (isset($propDefinition['enum'])) {
$enum = 'Enum\\' . ucfirst($this->kebabCaseToCamelCase($type .'-'. $name)) . '::class';
} elseif (isset($propDefinition['enum'])) {
$enum = 'Enum\\' . ucfirst($this->kebabCaseToCamelCase($type . '-' . $name)) . '::class';
} else {
// TODO create exception class for this one
throw new \Exception('Unknown property definition');
Expand All @@ -172,15 +171,15 @@ protected function generateSchema(string $type, array $modelSchema, array &$code
protected function generateFields(string $type, array $modelSchema, array &$code): void
{
foreach ($modelSchema['properties'] as $name => $propDefinition) {
if (isset($propDefinition['type']) && !isset($propDefinition['enum'])) {
if (isset($propDefinition['type']) && ! isset($propDefinition['enum'])) {
$propType = static::$propTypeMapping[$propDefinition['type']];
if ($propType == 'array' && isset($propDefinition['items']['$ref'])) {
$propType = $this->getType($propDefinition['items']['$ref']) . '[]';
}
} elseif (isset($propDefinition['$ref'])) {
$propType = $this->getType($propDefinition['$ref']);
} elseif (isset($propDefinition['enum'])) {
$propType = 'Enum\\' . ucfirst($this->kebabCaseToCamelCase($type .'-'. $name));
$propType = 'Enum\\' . ucfirst($this->kebabCaseToCamelCase($type . '-' . $name));
} else {
// TODO create exception class for this one
throw new \Exception('Unknown property definition');
Expand Down Expand Up @@ -317,7 +316,6 @@ protected function generateMonoFieldAccessors(array $modelSchema, array &$code):
}



protected function getType(string $ref): string
{
//strip #/components/schemas/
Expand Down Expand Up @@ -367,7 +365,7 @@ protected function getFieldsWithMonoFieldModelType(array $modelSchema): array
$propType = $propDefinition['type'];
}

if (!isset($this->specs['components']['schemas'][$propType])) {
if (! isset($this->specs['components']['schemas'][$propType])) {
continue;
}

Expand Down Expand Up @@ -403,7 +401,7 @@ protected function kebabCaseToCamelCase(string $name): string
$name = str_replace(' ', '-', $name);

$nameElems = explode('-', $name);
for ($i=1; $i<count($nameElems); $i++) {
for ($i = 1; $i < count($nameElems); $i++) {
$nameElems[$i] = ucfirst($nameElems[$i]);
}
return implode('', $nameElems);
Expand All @@ -420,7 +418,7 @@ protected function getEnumName(string $name): string
// We add the first `_` for enums starting with a integer character
$prefix = is_numeric($name[0]) ? '_' : '';

return $prefix.strtoupper($name);
return $prefix . strtoupper($name);
}

protected function wrapComment(string $comment, string $linePrefix, int $maxLength = 120): string
Expand Down
29 changes: 29 additions & 0 deletions src/OpenApi/SpecsDownloader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace Picqer\BolRetailerV10\OpenApi;

class SpecsDownloader
{
private const SPECS = [
[
'source' => 'https://api.bol.com/retailer/public/apispec/Retailer%20API%20-%20v10',
'target' => 'retailer.json',
],
[
'source' => 'https://api.bol.com/retailer/public/apispec/Shared%20API%20-%20v10',
'target' => 'shared.json',
],
];

public static function run(): void
{
foreach (static::SPECS as $spec) {
$sourceFile = file_get_contents($spec['source']);

// Tidy JSON formatting
$sourceTidied = json_encode(json_decode($sourceFile), JSON_PRETTY_PRINT + JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE);

file_put_contents(__DIR__ . DIRECTORY_SEPARATOR . $spec['target'], $sourceTidied);
}
}
}
Loading

0 comments on commit 45243ed

Please sign in to comment.