@apollo-deploy/tesseract 0.9.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/csharp/index.d.ts +12 -0
- package/dist/adapters/csharp/index.d.ts.map +1 -0
- package/dist/adapters/csharp/index.js +99 -0
- package/dist/adapters/csharp/index.js.map +1 -0
- package/dist/adapters/go/index.d.ts +12 -0
- package/dist/adapters/go/index.d.ts.map +1 -0
- package/dist/adapters/go/index.js +135 -0
- package/dist/adapters/go/index.js.map +1 -0
- package/dist/adapters/kotlin/index.d.ts +13 -0
- package/dist/adapters/kotlin/index.d.ts.map +1 -0
- package/dist/adapters/kotlin/index.js +188 -0
- package/dist/adapters/kotlin/index.js.map +1 -0
- package/dist/adapters/php/index.d.ts +20 -0
- package/dist/adapters/php/index.d.ts.map +1 -0
- package/dist/adapters/php/index.js +123 -0
- package/dist/adapters/php/index.js.map +1 -0
- package/dist/adapters/python/index.d.ts +12 -0
- package/dist/adapters/python/index.d.ts.map +1 -0
- package/dist/adapters/python/index.js +145 -0
- package/dist/adapters/python/index.js.map +1 -0
- package/dist/adapters/ruby/index.d.ts +12 -0
- package/dist/adapters/ruby/index.d.ts.map +1 -0
- package/dist/adapters/ruby/index.js +86 -0
- package/dist/adapters/ruby/index.js.map +1 -0
- package/dist/adapters/rust/index.d.ts +13 -0
- package/dist/adapters/rust/index.d.ts.map +1 -0
- package/dist/adapters/rust/index.js +213 -0
- package/dist/adapters/rust/index.js.map +1 -0
- package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +5 -3
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
- package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
- package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
- package/dist/adapters/typescript/index.d.ts.map +1 -1
- package/dist/adapters/typescript/index.js +43 -2
- package/dist/adapters/typescript/index.js.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/intake.d.ts.map +1 -1
- package/dist/pipeline/intake.js +32 -21
- package/dist/pipeline/intake.js.map +1 -1
- package/dist/types/ir.d.ts +1 -1
- package/dist/types/ir.d.ts.map +1 -1
- package/dist/types/sdk-module.d.ts +22 -2
- package/dist/types/sdk-module.d.ts.map +1 -1
- package/dist/types/sdk-module.js +1 -1
- package/package.json +1 -1
- package/templates/csharp/client.hbs +95 -0
- package/templates/csharp/csproj.hbs +18 -0
- package/templates/csharp/domain.hbs +183 -0
- package/templates/csharp/errors.hbs +45 -0
- package/templates/csharp/readme.hbs +76 -0
- package/templates/csharp/transport.hbs +291 -0
- package/templates/csharp/types.hbs +104 -0
- package/templates/go/client.hbs +158 -0
- package/templates/go/domain.hbs +89 -0
- package/templates/go/errors.hbs +38 -0
- package/templates/go/go-mod.hbs +6 -0
- package/templates/go/readme.hbs +73 -0
- package/templates/go/transport.hbs +379 -0
- package/templates/go/types.hbs +80 -0
- package/templates/go/utils.hbs +45 -0
- package/templates/kotlin/build-gradle.hbs +151 -0
- package/templates/kotlin/client.hbs +57 -0
- package/templates/kotlin/domain.hbs +73 -0
- package/templates/kotlin/errors.hbs +42 -0
- package/templates/kotlin/gradle-properties.hbs +2 -0
- package/templates/kotlin/readme.hbs +133 -0
- package/templates/kotlin/settings-gradle.hbs +1 -0
- package/templates/kotlin/transport.hbs +246 -0
- package/templates/kotlin/types.hbs +100 -0
- package/templates/php/client.hbs +74 -0
- package/templates/php/composer.hbs +19 -0
- package/templates/php/domain.hbs +173 -0
- package/templates/php/errors.hbs +131 -0
- package/templates/php/readme.hbs +89 -0
- package/templates/php/transport.hbs +285 -0
- package/templates/php/types.hbs +95 -0
- package/templates/python/client.hbs +102 -0
- package/templates/python/domain.hbs +139 -0
- package/templates/python/index.hbs +20 -0
- package/templates/python/pyproject.hbs +26 -0
- package/templates/python/readme.hbs +64 -0
- package/templates/python/resources-init.hbs +11 -0
- package/templates/python/transport.hbs +191 -0
- package/templates/python/types-errors.hbs +75 -0
- package/templates/python/types.hbs +79 -0
- package/templates/ruby/Gemfile.hbs +5 -0
- package/templates/ruby/client.hbs +86 -0
- package/templates/ruby/domain.hbs +110 -0
- package/templates/ruby/errors.hbs +150 -0
- package/templates/ruby/gemspec.hbs +30 -0
- package/templates/ruby/index.hbs +54 -0
- package/templates/ruby/readme.hbs +113 -0
- package/templates/ruby/transport.hbs +177 -0
- package/templates/ruby/types.hbs +126 -0
- package/templates/ruby/version.hbs +5 -0
- package/templates/rust/api-mod.hbs +3 -0
- package/templates/rust/cargo.hbs +22 -0
- package/templates/rust/client.hbs +114 -0
- package/templates/rust/domain.hbs +94 -0
- package/templates/rust/error.hbs +74 -0
- package/templates/rust/lib.hbs +85 -0
- package/templates/rust/readme.hbs +77 -0
- package/templates/rust/transport.hbs +287 -0
- package/templates/rust/types.hbs +142 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @generated
|
|
5
|
+
* This file was automatically generated by Tesseract.
|
|
6
|
+
* DO NOT MODIFY THIS FILE DIRECTLY.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare(strict_types=1);
|
|
10
|
+
|
|
11
|
+
namespace {{namespace}};
|
|
12
|
+
|
|
13
|
+
use Throwable;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Standard SDK error class.
|
|
17
|
+
*
|
|
18
|
+
* Wraps HTTP errors and transport failures with structured metadata
|
|
19
|
+
* including HTTP status, error code, and request ID for debugging.
|
|
20
|
+
*/
|
|
21
|
+
class SDKError extends \Exception
|
|
22
|
+
{
|
|
23
|
+
/**
|
|
24
|
+
* HTTP status code (0 for non-HTTP / connection errors).
|
|
25
|
+
*/
|
|
26
|
+
private int $httpStatus;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Machine-readable error code (e.g. "NOT_FOUND", "VALIDATION_ERROR").
|
|
30
|
+
*/
|
|
31
|
+
private string $errorCode;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The x-request-id header value, if present in the response.
|
|
35
|
+
*/
|
|
36
|
+
private ?string $requestId;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Raw response body for inspection.
|
|
40
|
+
*
|
|
41
|
+
* @var array<string, mixed>|null
|
|
42
|
+
*/
|
|
43
|
+
private ?array $responseBody;
|
|
44
|
+
|
|
45
|
+
public function __construct(
|
|
46
|
+
string $message = 'An SDK error occurred',
|
|
47
|
+
int $status = 0,
|
|
48
|
+
string $code = 'SDK_ERROR',
|
|
49
|
+
?string $requestId = null,
|
|
50
|
+
?array $responseBody = null,
|
|
51
|
+
?Throwable $previous = null,
|
|
52
|
+
) {
|
|
53
|
+
parent::__construct($message, $status, $previous);
|
|
54
|
+
|
|
55
|
+
$this->httpStatus = $status;
|
|
56
|
+
$this->errorCode = $code;
|
|
57
|
+
$this->requestId = $requestId;
|
|
58
|
+
$this->responseBody = $responseBody;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create an SDKError from a generic Throwable.
|
|
63
|
+
*/
|
|
64
|
+
public static function fromException(Throwable $exception): self
|
|
65
|
+
{
|
|
66
|
+
return new self(
|
|
67
|
+
message: $exception->getMessage(),
|
|
68
|
+
status: $exception->getCode(),
|
|
69
|
+
code: 'TRANSPORT_ERROR',
|
|
70
|
+
previous: $exception,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the HTTP status code.
|
|
76
|
+
*/
|
|
77
|
+
public function getHttpStatus(): int
|
|
78
|
+
{
|
|
79
|
+
return $this->httpStatus;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get the machine-readable error code.
|
|
84
|
+
*/
|
|
85
|
+
public function getErrorCode(): string
|
|
86
|
+
{
|
|
87
|
+
return $this->errorCode;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the request ID from the x-request-id header, if available.
|
|
92
|
+
*/
|
|
93
|
+
public function getRequestId(): ?string
|
|
94
|
+
{
|
|
95
|
+
return $this->requestId;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the decoded response body, if available.
|
|
100
|
+
*
|
|
101
|
+
* @return array<string, mixed>|null
|
|
102
|
+
*/
|
|
103
|
+
public function getResponseBody(): ?array
|
|
104
|
+
{
|
|
105
|
+
return $this->responseBody;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Check if this error is retryable (status >= 500 or 429).
|
|
110
|
+
*/
|
|
111
|
+
public function isRetryable(): bool
|
|
112
|
+
{
|
|
113
|
+
return $this->httpStatus >= 500 || $this->httpStatus === 429;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if this is a client error (4xx).
|
|
118
|
+
*/
|
|
119
|
+
public function isClientError(): bool
|
|
120
|
+
{
|
|
121
|
+
return $this->httpStatus >= 400 && $this->httpStatus < 500;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if this is a server error (5xx).
|
|
126
|
+
*/
|
|
127
|
+
public function isServerError(): bool
|
|
128
|
+
{
|
|
129
|
+
return $this->httpStatus >= 500;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# {{title}} PHP SDK
|
|
2
|
+
|
|
3
|
+
{{#if description}}
|
|
4
|
+
{{description}}
|
|
5
|
+
|
|
6
|
+
{{/if}}
|
|
7
|
+
> This SDK was automatically generated by [Tesseract](https://www.npmjs.com/package/@apollo-deploy/tesseract).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
composer require {{composerName}}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```php
|
|
18
|
+
<?php
|
|
19
|
+
|
|
20
|
+
require_once __DIR__ . '/vendor/autoload.php';
|
|
21
|
+
|
|
22
|
+
use {{namespace}}\Client;
|
|
23
|
+
|
|
24
|
+
$client = new Client([
|
|
25
|
+
'baseUrl' => '{{baseUrl}}',
|
|
26
|
+
{{#each securitySchemes}}
|
|
27
|
+
'{{configKey}}' => 'your-{{kebabCase name}}-credential',
|
|
28
|
+
{{/each}}
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
{{#with groups.[0]}}
|
|
32
|
+
$api = $client->{{name}}();
|
|
33
|
+
{{/with}}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
The `Client` constructor accepts an associative array with the following keys:
|
|
39
|
+
|
|
40
|
+
| Option | Type | Default | Description |
|
|
41
|
+
|--------|------|---------|-------------|
|
|
42
|
+
| `baseUrl` | `string` | `'{{baseUrl}}'` | Base URL for the API |
|
|
43
|
+
{{#each securitySchemes}}
|
|
44
|
+
| `{{configKey}}` | `string` | `null` | {{#if description}}{{description}}{{else}}{{type}} credential for {{name}}{{/if}} |
|
|
45
|
+
{{/each}}
|
|
46
|
+
| `timeoutMs` | `int` | `15000` | Request timeout in milliseconds |
|
|
47
|
+
| `retries` | `array` | `['attempts' => 3, 'backoffMs' => 400, 'jitter' => true]` | Retry configuration |
|
|
48
|
+
| `defaultHeaders` | `array` | `[]` | Default headers sent with every request |
|
|
49
|
+
|
|
50
|
+
## API Reference
|
|
51
|
+
|
|
52
|
+
{{#each groups}}
|
|
53
|
+
### `{{className}}`
|
|
54
|
+
|
|
55
|
+
{{#if description}}
|
|
56
|
+
{{description}}
|
|
57
|
+
|
|
58
|
+
{{/if}}
|
|
59
|
+
| Method | HTTP | Path | Description |
|
|
60
|
+
|--------|------|------|-------------|
|
|
61
|
+
{{#each operations}}
|
|
62
|
+
| `{{name}}()` | `{{upperCase httpMethod}}` | `{{path}}` | {{#if description}}{{description}}{{else}}-{{/if}} |
|
|
63
|
+
{{/each}}
|
|
64
|
+
|
|
65
|
+
{{/each}}
|
|
66
|
+
|
|
67
|
+
## Types
|
|
68
|
+
|
|
69
|
+
{{#each schemas}}
|
|
70
|
+
{{#if isEnum}}
|
|
71
|
+
- `{{name}}` — enum: {{#each enumValues}}`{{this}}`{{#unless @last}}, {{/unless}}{{/each}}
|
|
72
|
+
{{else}}
|
|
73
|
+
- `{{name}}`{{#if description}} — {{description}}{{/if}}
|
|
74
|
+
{{/if}}
|
|
75
|
+
{{/each}}
|
|
76
|
+
|
|
77
|
+
## Error Handling
|
|
78
|
+
|
|
79
|
+
```php
|
|
80
|
+
<?php
|
|
81
|
+
|
|
82
|
+
use {{namespace}}\SDKError;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
$result = $client->{{groups.[0].name}}()->{{groups.[0].operations.[0].name}}(/* params */);
|
|
86
|
+
} catch (SDKError $e) {
|
|
87
|
+
echo sprintf("Error %d: %s (%s)\n", $e->getHttpStatus(), $e->getMessage(), $e->getErrorCode());
|
|
88
|
+
}
|
|
89
|
+
```
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @generated
|
|
5
|
+
* This file was automatically generated by Tesseract.
|
|
6
|
+
* DO NOT MODIFY THIS FILE DIRECTLY.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare(strict_types=1);
|
|
10
|
+
|
|
11
|
+
namespace {{namespace}};
|
|
12
|
+
|
|
13
|
+
use GuzzleHttp\Client as GuzzleClient;
|
|
14
|
+
use GuzzleHttp\ClientInterface;
|
|
15
|
+
use GuzzleHttp\Exception\ConnectException;
|
|
16
|
+
use GuzzleHttp\Exception\RequestException;
|
|
17
|
+
use GuzzleHttp\Psr7\Request;
|
|
18
|
+
use GuzzleHttp\RequestOptions;
|
|
19
|
+
use Psr\Http\Message\ResponseInterface;
|
|
20
|
+
use Throwable;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* HTTP transport layer with retry logic and security scheme injection.
|
|
24
|
+
*
|
|
25
|
+
* Wraps a GuzzleHttp\Client and provides executeRequest() for typed
|
|
26
|
+
* request/response handling with automatic auth header injection
|
|
27
|
+
* and configurable retry with exponential backoff + jitter.
|
|
28
|
+
*/
|
|
29
|
+
class Transport
|
|
30
|
+
{
|
|
31
|
+
private ClientInterface $http;
|
|
32
|
+
|
|
33
|
+
/** @var array<string, mixed> */
|
|
34
|
+
private array $config;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param array{
|
|
38
|
+
* baseUrl?: string,
|
|
39
|
+
* timeoutMs?: int,
|
|
40
|
+
* retries?: array{attempts?: int, backoffMs?: int, jitter?: bool, maxBackoffMs?: int},
|
|
41
|
+
* defaultHeaders?: array<string, string>,
|
|
42
|
+
{{#each securitySchemes}}
|
|
43
|
+
* {{configKey}}?: string,
|
|
44
|
+
{{/each}}
|
|
45
|
+
* } $config
|
|
46
|
+
*/
|
|
47
|
+
public function __construct(array $config = [])
|
|
48
|
+
{
|
|
49
|
+
$this->config = $config;
|
|
50
|
+
|
|
51
|
+
$baseUrl = rtrim($config['baseUrl'] ?? '{{baseUrl}}', '/');
|
|
52
|
+
$timeoutMs = $config['timeoutMs'] ?? 15000;
|
|
53
|
+
|
|
54
|
+
$headers = $config['defaultHeaders'] ?? [];
|
|
55
|
+
$headers['Accept'] = $headers['Accept'] ?? 'application/json';
|
|
56
|
+
$headers['User-Agent'] = $headers['User-Agent'] ?? '{{clientName}}-php-sdk/{{version}}';
|
|
57
|
+
|
|
58
|
+
$this->http = new GuzzleClient([
|
|
59
|
+
'base_uri' => $baseUrl . '/',
|
|
60
|
+
'timeout' => $timeoutMs / 1000.0,
|
|
61
|
+
'http_errors' => false,
|
|
62
|
+
'headers' => $headers,
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Execute an HTTP request with retry logic and auth injection.
|
|
68
|
+
*
|
|
69
|
+
* @param array{
|
|
70
|
+
* method: string,
|
|
71
|
+
* path: string,
|
|
72
|
+
* query?: array<string, mixed>,
|
|
73
|
+
* body?: mixed,
|
|
74
|
+
* headers?: array<string, string>,
|
|
75
|
+
* timeoutMs?: int,
|
|
76
|
+
* } $requestDef
|
|
77
|
+
* @return ResponseInterface
|
|
78
|
+
* @throws SDKError
|
|
79
|
+
*/
|
|
80
|
+
public function executeRequest(array $requestDef): ResponseInterface
|
|
81
|
+
{
|
|
82
|
+
$method = strtoupper($requestDef['method']);
|
|
83
|
+
$path = ltrim($requestDef['path'], '/');
|
|
84
|
+
$query = $requestDef['query'] ?? [];
|
|
85
|
+
$body = $requestDef['body'] ?? null;
|
|
86
|
+
$extraHeaders = $requestDef['headers'] ?? [];
|
|
87
|
+
$timeoutMs = $requestDef['timeoutMs'] ?? null;
|
|
88
|
+
|
|
89
|
+
// ── Inject auth headers ────────────────────────────────────────────
|
|
90
|
+
$authHeaders = $this->buildAuthHeaders();
|
|
91
|
+
|
|
92
|
+
$options = [
|
|
93
|
+
RequestOptions::HEADERS => array_merge($authHeaders, $extraHeaders),
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
if (!empty($query)) {
|
|
97
|
+
$options[RequestOptions::QUERY] = $query;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if ($body !== null) {
|
|
101
|
+
$options[RequestOptions::JSON] = $body;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if ($timeoutMs !== null) {
|
|
105
|
+
$options[RequestOptions::TIMEOUT] = $timeoutMs / 1000.0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Execute with retry ─────────────────────────────────────────────
|
|
109
|
+
return $this->executeWithRetry($method, $path, $options);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Execute with configurable retry logic.
|
|
114
|
+
*/
|
|
115
|
+
private function executeWithRetry(string $method, string $path, array $options): ResponseInterface
|
|
116
|
+
{
|
|
117
|
+
$retryConfig = $this->config['retries'] ?? [];
|
|
118
|
+
$maxAttempts = $retryConfig['attempts'] ?? 3;
|
|
119
|
+
$baseBackoffMs = $retryConfig['backoffMs'] ?? 400;
|
|
120
|
+
$useJitter = $retryConfig['jitter'] ?? true;
|
|
121
|
+
$maxBackoffMs = $retryConfig['maxBackoffMs'] ?? 30000;
|
|
122
|
+
|
|
123
|
+
$lastException = null;
|
|
124
|
+
|
|
125
|
+
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
|
|
126
|
+
try {
|
|
127
|
+
$request = new Request($method, $path, $options[RequestOptions::HEADERS] ?? []);
|
|
128
|
+
$response = $this->http->send($request, $options);
|
|
129
|
+
|
|
130
|
+
// Retry on server errors (5xx) and 429 (rate limit)
|
|
131
|
+
$status = $response->getStatusCode();
|
|
132
|
+
if ($status < 500 && $status !== 429) {
|
|
133
|
+
return $response;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if ($attempt < $maxAttempts) {
|
|
137
|
+
$delayMs = $this->calculateBackoff($attempt, $baseBackoffMs, $useJitter, $maxBackoffMs);
|
|
138
|
+
$this->sleep($delayMs);
|
|
139
|
+
}
|
|
140
|
+
} catch (ConnectException $e) {
|
|
141
|
+
$lastException = $e;
|
|
142
|
+
|
|
143
|
+
if ($attempt < $maxAttempts) {
|
|
144
|
+
$delayMs = $this->calculateBackoff($attempt, $baseBackoffMs, $useJitter, $maxBackoffMs);
|
|
145
|
+
$this->sleep($delayMs);
|
|
146
|
+
}
|
|
147
|
+
} catch (RequestException $e) {
|
|
148
|
+
$response = $e->getResponse();
|
|
149
|
+
if ($response !== null) {
|
|
150
|
+
$status = $response->getStatusCode();
|
|
151
|
+
if ($status >= 500 || $status === 429) {
|
|
152
|
+
$lastException = $e;
|
|
153
|
+
if ($attempt < $maxAttempts) {
|
|
154
|
+
$delayMs = $this->calculateBackoff($attempt, $baseBackoffMs, $useJitter, $maxBackoffMs);
|
|
155
|
+
$this->sleep($delayMs);
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
throw $this->mapToSDKError($e);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// All retries exhausted
|
|
165
|
+
if ($lastException instanceof RequestException) {
|
|
166
|
+
throw $this->mapToSDKError($lastException);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if ($lastException instanceof Throwable) {
|
|
170
|
+
throw SDKError::fromException($lastException);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new SDKError('Request failed after ' . $maxAttempts . ' attempts');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Build auth headers from configured security schemes.
|
|
178
|
+
*
|
|
179
|
+
* @return array<string, string>
|
|
180
|
+
*/
|
|
181
|
+
private function buildAuthHeaders(): array
|
|
182
|
+
{
|
|
183
|
+
$headers = [];
|
|
184
|
+
|
|
185
|
+
{{#each securitySchemes}}
|
|
186
|
+
{{#if (eq type "apiKey")}}
|
|
187
|
+
{{#if (eq in "header")}}
|
|
188
|
+
// {{name}} — API key in header
|
|
189
|
+
if (!empty($this->config['{{configKey}}'])) {
|
|
190
|
+
$headers['{{paramName}}'] = $this->config['{{configKey}}'];
|
|
191
|
+
}
|
|
192
|
+
{{/if}}
|
|
193
|
+
{{/if}}
|
|
194
|
+
|
|
195
|
+
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
196
|
+
// {{name}} — Bearer token
|
|
197
|
+
if (!empty($this->config['{{configKey}}'])) {
|
|
198
|
+
$headers['Authorization'] = 'Bearer ' . $this->config['{{configKey}}'];
|
|
199
|
+
}
|
|
200
|
+
{{/if}}
|
|
201
|
+
|
|
202
|
+
{{#if (eq type "oauth2")}}
|
|
203
|
+
// {{name}} — OAuth2 access token
|
|
204
|
+
if (!empty($this->config['{{configKey}}'])) {
|
|
205
|
+
$headers['Authorization'] = 'Bearer ' . $this->config['{{configKey}}'];
|
|
206
|
+
}
|
|
207
|
+
{{/if}}
|
|
208
|
+
|
|
209
|
+
{{#if (eq type "openIdConnect")}}
|
|
210
|
+
// {{name}} — OpenID Connect token
|
|
211
|
+
if (!empty($this->config['{{configKey}}'])) {
|
|
212
|
+
$headers['Authorization'] = 'Bearer ' . $this->config['{{configKey}}'];
|
|
213
|
+
}
|
|
214
|
+
{{/if}}
|
|
215
|
+
{{/each}}
|
|
216
|
+
|
|
217
|
+
return $headers;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Calculate exponential backoff with optional jitter.
|
|
222
|
+
*/
|
|
223
|
+
private function calculateBackoff(int $attempt, int $baseMs, bool $jitter, int $maxMs): int
|
|
224
|
+
{
|
|
225
|
+
$delay = $baseMs * (2 ** ($attempt - 1));
|
|
226
|
+
|
|
227
|
+
if ($jitter) {
|
|
228
|
+
$delay = (int) ($delay * (0.5 + (mt_rand() / mt_getrandmax()) * 0.5));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return min($delay, $maxMs);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Sleep for a given number of milliseconds.
|
|
236
|
+
*/
|
|
237
|
+
private function sleep(int $milliseconds): void
|
|
238
|
+
{
|
|
239
|
+
usleep($milliseconds * 1000);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Map a Guzzle RequestException to an SDKError.
|
|
244
|
+
*/
|
|
245
|
+
private function mapToSDKError(RequestException $exception): SDKError
|
|
246
|
+
{
|
|
247
|
+
$response = $exception->getResponse();
|
|
248
|
+
|
|
249
|
+
if ($response === null) {
|
|
250
|
+
return SDKError::fromException($exception);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
$status = $response->getStatusCode();
|
|
254
|
+
$body = $this->decodeResponseBody($response);
|
|
255
|
+
|
|
256
|
+
$message = $body['message'] ?? $body['error'] ?? $exception->getMessage();
|
|
257
|
+
$code = $body['code'] ?? $body['error_code'] ?? 'UNKNOWN';
|
|
258
|
+
$requestId = $response->getHeaderLine('x-request-id') ?: null;
|
|
259
|
+
|
|
260
|
+
return new SDKError(
|
|
261
|
+
message: (string) $message,
|
|
262
|
+
status: $status,
|
|
263
|
+
code: (string) $code,
|
|
264
|
+
requestId: $requestId,
|
|
265
|
+
previous: $exception,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Decode a PSR-7 response body, returning an associative array.
|
|
271
|
+
*
|
|
272
|
+
* @return array<string, mixed>
|
|
273
|
+
*/
|
|
274
|
+
private function decodeResponseBody(ResponseInterface $response): array
|
|
275
|
+
{
|
|
276
|
+
$contents = $response->getBody()->__toString();
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
$decoded = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
|
|
280
|
+
return is_array($decoded) ? $decoded : ['raw' => $decoded];
|
|
281
|
+
} catch (Throwable) {
|
|
282
|
+
return ['raw' => $contents];
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @generated
|
|
5
|
+
* This file was automatically generated by Tesseract.
|
|
6
|
+
* DO NOT MODIFY THIS FILE DIRECTLY.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare(strict_types=1);
|
|
10
|
+
|
|
11
|
+
namespace {{namespace}};
|
|
12
|
+
|
|
13
|
+
{{#each schemas}}
|
|
14
|
+
{{#if isEnum}}
|
|
15
|
+
/**
|
|
16
|
+
{{#if description}}
|
|
17
|
+
* {{description}}
|
|
18
|
+
*
|
|
19
|
+
{{/if}}
|
|
20
|
+
* @generated
|
|
21
|
+
*/
|
|
22
|
+
enum {{name}}: {{#each properties}}{{#if @first}}{{type}}{{/if}}{{/each}}
|
|
23
|
+
{
|
|
24
|
+
{{#each enumValues}}
|
|
25
|
+
case {{this}} = '{{this}}';
|
|
26
|
+
{{/each}}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
{{else if isTypeAlias}}
|
|
30
|
+
{{!-- PHP type aliases are not direct; they are used as docblock hints --}}
|
|
31
|
+
{{!-- Skipping type alias: {{name}} --}}
|
|
32
|
+
|
|
33
|
+
{{else if isUnionType}}
|
|
34
|
+
{{!-- PHP union types are handled via properties, not standalone declarations --}}
|
|
35
|
+
{{!-- Skipping union type: {{name}} --}}
|
|
36
|
+
|
|
37
|
+
{{else}}
|
|
38
|
+
/**
|
|
39
|
+
{{#if description}}
|
|
40
|
+
* {{description}}
|
|
41
|
+
*
|
|
42
|
+
{{/if}}
|
|
43
|
+
* @generated
|
|
44
|
+
*/
|
|
45
|
+
class {{name}}{{#if extends}} extends {{extends}}{{/if}}
|
|
46
|
+
{
|
|
47
|
+
{{#each properties}}
|
|
48
|
+
/**
|
|
49
|
+
* @var {{tsType type nullable}}
|
|
50
|
+
*/
|
|
51
|
+
public {{#if nullable}}?{{/if}}{{type}} ${{name}}{{#if defaultValue}} = {{{json defaultValue}}}{{/if}};
|
|
52
|
+
|
|
53
|
+
{{/each}}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param array<string, mixed> $data
|
|
57
|
+
*/
|
|
58
|
+
public static function fromArray(array $data): self
|
|
59
|
+
{
|
|
60
|
+
$instance = new self();
|
|
61
|
+
|
|
62
|
+
{{#each properties}}
|
|
63
|
+
{{#if (eq type "string")}}
|
|
64
|
+
$instance->{{name}} = isset($data['{{name}}']) ? (string) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
|
|
65
|
+
{{else if (eq type "int")}}
|
|
66
|
+
$instance->{{name}} = isset($data['{{name}}']) ? (int) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
|
|
67
|
+
{{else if (eq type "float")}}
|
|
68
|
+
$instance->{{name}} = isset($data['{{name}}']) ? (float) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
|
|
69
|
+
{{else if (eq type "bool")}}
|
|
70
|
+
$instance->{{name}} = isset($data['{{name}}']) ? (bool) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
|
|
71
|
+
{{else if (eq type "array")}}
|
|
72
|
+
$instance->{{name}} = isset($data['{{name}}']) ? (array) $data['{{name}}'] : {{#if required}}[]{{else}}null{{/if}};
|
|
73
|
+
{{else}}
|
|
74
|
+
$instance->{{name}} = $data['{{name}}'] ?? {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
|
|
75
|
+
{{/if}}
|
|
76
|
+
|
|
77
|
+
{{/each}}
|
|
78
|
+
return $instance;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @return array<string, mixed>
|
|
83
|
+
*/
|
|
84
|
+
public function toArray(): array
|
|
85
|
+
{
|
|
86
|
+
return [
|
|
87
|
+
{{#each properties}}
|
|
88
|
+
'{{name}}' => $this->{{name}},
|
|
89
|
+
{{/each}}
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
{{/if}}
|
|
95
|
+
{{/each}}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""{{title}} SDK Client"""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any, List, Callable, Union
|
|
4
|
+
import httpx
|
|
5
|
+
from .transport import create_transport, SDKTransport, RequestOptions
|
|
6
|
+
{{#each groups}}
|
|
7
|
+
from .resources.{{fileName}} import {{interfaceName}}
|
|
8
|
+
{{/each}}
|
|
9
|
+
|
|
10
|
+
{{#if hasSSE}}
|
|
11
|
+
# SSE support requires the sseclient-py package:
|
|
12
|
+
# pip install sseclient-py>=1.8.0
|
|
13
|
+
{{/if}}
|
|
14
|
+
|
|
15
|
+
class {{clientName}}ClientConfig:
|
|
16
|
+
"""Configuration for {{title}} client."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
base_url: str = "{{baseUrl}}",
|
|
21
|
+
{{#each securitySchemes}}
|
|
22
|
+
{{#if (eq type "apiKey")}}
|
|
23
|
+
{{configKey}}: Optional[str] = None,
|
|
24
|
+
{{/if}}
|
|
25
|
+
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
26
|
+
{{configKey}}: Optional[str] = None,
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{#if (eq type "oauth2")}}
|
|
29
|
+
{{configKey}}: Optional[str] = None,
|
|
30
|
+
{{/if}}
|
|
31
|
+
{{#if (eq type "openIdConnect")}}
|
|
32
|
+
{{configKey}}: Optional[str] = None,
|
|
33
|
+
{{/if}}
|
|
34
|
+
{{/each}}
|
|
35
|
+
timeout_ms: int = 15000,
|
|
36
|
+
retries: int = 3,
|
|
37
|
+
default_headers: Optional[Dict[str, str]] = None,
|
|
38
|
+
on_request: Optional[Callable] = None,
|
|
39
|
+
on_response: Optional[Callable] = None,
|
|
40
|
+
on_error: Optional[Callable] = None,
|
|
41
|
+
):
|
|
42
|
+
self.base_url = base_url
|
|
43
|
+
{{#each securitySchemes}}
|
|
44
|
+
self.{{configKey}} = {{configKey}}
|
|
45
|
+
{{/each}}
|
|
46
|
+
self.timeout_ms = timeout_ms
|
|
47
|
+
self.retries = retries
|
|
48
|
+
self.default_headers = default_headers or {}
|
|
49
|
+
self.on_request = on_request
|
|
50
|
+
self.on_response = on_response
|
|
51
|
+
self.on_error = on_error
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class {{clientName}}Client:
|
|
55
|
+
"""{{title}} SDK Client"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, config: Optional[{{clientName}}ClientConfig] = None):
|
|
58
|
+
self.config = config or {{clientName}}ClientConfig()
|
|
59
|
+
self._transport = create_transport(
|
|
60
|
+
base_url=self.config.base_url,
|
|
61
|
+
{{#each securitySchemes}}
|
|
62
|
+
{{configKey}}=self.config.{{configKey}},
|
|
63
|
+
{{/each}}
|
|
64
|
+
timeout_ms=self.config.timeout_ms,
|
|
65
|
+
retries=self.config.retries,
|
|
66
|
+
default_headers=self.config.default_headers,
|
|
67
|
+
on_request=self.config.on_request,
|
|
68
|
+
on_response=self.config.on_response,
|
|
69
|
+
on_error=self.config.on_error,
|
|
70
|
+
)
|
|
71
|
+
{{#each groups}}
|
|
72
|
+
self.{{snakeCase name}} = {{interfaceName}}(self._transport)
|
|
73
|
+
{{/each}}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def create_{{snakeCase clientName}}_client(
|
|
77
|
+
base_url: str = "{{baseUrl}}",
|
|
78
|
+
{{#each securitySchemes}}
|
|
79
|
+
{{#if (eq type "apiKey")}}
|
|
80
|
+
{{configKey}}: Optional[str] = None,
|
|
81
|
+
{{/if}}
|
|
82
|
+
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
83
|
+
{{configKey}}: Optional[str] = None,
|
|
84
|
+
{{/if}}
|
|
85
|
+
{{#if (eq type "oauth2")}}
|
|
86
|
+
{{configKey}}: Optional[str] = None,
|
|
87
|
+
{{/if}}
|
|
88
|
+
{{#if (eq type "openIdConnect")}}
|
|
89
|
+
{{configKey}}: Optional[str] = None,
|
|
90
|
+
{{/if}}
|
|
91
|
+
{{/each}}
|
|
92
|
+
**kwargs,
|
|
93
|
+
) -> {{clientName}}Client:
|
|
94
|
+
"""Create a new {{title}} SDK client."""
|
|
95
|
+
config = {{clientName}}ClientConfig(
|
|
96
|
+
base_url=base_url,
|
|
97
|
+
{{#each securitySchemes}}
|
|
98
|
+
{{configKey}}={{configKey}},
|
|
99
|
+
{{/each}}
|
|
100
|
+
**kwargs,
|
|
101
|
+
)
|
|
102
|
+
return {{clientName}}Client(config)
|