@apollo-deploy/tesseract 0.9.1 → 1.0.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 +186 -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 +2 -2
- 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/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.js +23 -16
- 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 +41 -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 +105 -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,173 @@
|
|
|
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}}\Api;
|
|
12
|
+
|
|
13
|
+
use Psr\Http\Message\ResponseInterface;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* {{interfaceName}} domain operations.
|
|
17
|
+
*
|
|
18
|
+
{{#if description}}
|
|
19
|
+
* {{description}}
|
|
20
|
+
*
|
|
21
|
+
{{/if}}
|
|
22
|
+
* @generated
|
|
23
|
+
*/
|
|
24
|
+
class {{className}}
|
|
25
|
+
{
|
|
26
|
+
private Transport $transport;
|
|
27
|
+
|
|
28
|
+
public function __construct(Transport $transport)
|
|
29
|
+
{
|
|
30
|
+
$this->transport = $transport;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
{{#each operations}}
|
|
34
|
+
{{#if (isEventStream this)}}
|
|
35
|
+
// SSE operations are not yet supported in the PHP adapter — skipping {{name}}
|
|
36
|
+
|
|
37
|
+
{{else}}
|
|
38
|
+
/**
|
|
39
|
+
{{#if deprecated}}
|
|
40
|
+
* @deprecated{{#if deprecationMessage}} {{deprecationMessage}}{{/if}}
|
|
41
|
+
*
|
|
42
|
+
{{/if}}
|
|
43
|
+
{{#if (or summary description)}}
|
|
44
|
+
* {{#if summary}}{{summary}}{{#if description}} — {{description}}{{/if}}{{else}}{{description}}{{/if}}
|
|
45
|
+
*
|
|
46
|
+
{{/if}}
|
|
47
|
+
{{#each pathParams}}
|
|
48
|
+
* @param {{type}} ${{name}}{{#if description}} {{description}}{{/if}}
|
|
49
|
+
{{/each}}
|
|
50
|
+
{{#if requestBody}}
|
|
51
|
+
* @param array<string, mixed>{{#unless requestBody.required}}|null{{/unless}} $input Request body
|
|
52
|
+
{{/if}}
|
|
53
|
+
{{#each queryParams}}
|
|
54
|
+
* @param {{type}}|null ${{name}}{{#if description}} {{description}}{{/if}}
|
|
55
|
+
{{/each}}
|
|
56
|
+
{{#each headerParams}}
|
|
57
|
+
* @param {{type}}|null ${{name}}{{#if description}} {{description}}{{/if}}
|
|
58
|
+
{{/each}}
|
|
59
|
+
* @return {{responseType}}
|
|
60
|
+
* @throws SDKError
|
|
61
|
+
*/
|
|
62
|
+
public function {{name}}(
|
|
63
|
+
{{#each pathParams}}
|
|
64
|
+
{{type}} ${{name}},
|
|
65
|
+
{{/each}}
|
|
66
|
+
{{#if requestBody}}
|
|
67
|
+
{{#unless requestBody.required}}?{{/unless}}array $input{{#unless requestBody.required}} = null{{/unless}},
|
|
68
|
+
{{/if}}
|
|
69
|
+
{{#each queryParams}}
|
|
70
|
+
?{{type}} ${{name}} = null,
|
|
71
|
+
{{/each}}
|
|
72
|
+
{{#each headerParams}}
|
|
73
|
+
?{{type}} ${{name}} = null,
|
|
74
|
+
{{/each}}
|
|
75
|
+
): {{responseType}}
|
|
76
|
+
{
|
|
77
|
+
// Build path with parameter substitution
|
|
78
|
+
$path = '{{path}}';
|
|
79
|
+
{{#if (hasPathParams this)}}
|
|
80
|
+
$path = str_replace(
|
|
81
|
+
[{{#each pathParams}}'{{@root.LBRACE}}{{originalName}}{{@root.RBRACE}}'{{#unless @last}}, {{/unless}}{{/each}}],
|
|
82
|
+
[{{#each pathParams}}${{name}}{{#unless @last}}, {{/unless}}{{/each}}],
|
|
83
|
+
$path
|
|
84
|
+
);
|
|
85
|
+
{{/if}}
|
|
86
|
+
|
|
87
|
+
// Build query parameters
|
|
88
|
+
$query = [];
|
|
89
|
+
{{#if (hasQueryParams this)}}
|
|
90
|
+
{{#each queryParams}}
|
|
91
|
+
if (${{name}} !== null) {
|
|
92
|
+
$query['{{originalName}}'] = ${{name}};
|
|
93
|
+
}
|
|
94
|
+
{{/each}}
|
|
95
|
+
{{/if}}
|
|
96
|
+
|
|
97
|
+
// Build extra headers
|
|
98
|
+
$headers = [];
|
|
99
|
+
{{#each headerParams}}
|
|
100
|
+
if (${{name}} !== null) {
|
|
101
|
+
$headers['{{originalName}}'] = (string) ${{name}};
|
|
102
|
+
}
|
|
103
|
+
{{/each}}
|
|
104
|
+
|
|
105
|
+
// Execute request
|
|
106
|
+
$requestDef = [
|
|
107
|
+
'method' => '{{httpMethod}}',
|
|
108
|
+
'path' => $path,
|
|
109
|
+
'query' => $query,
|
|
110
|
+
'headers' => $headers,
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
{{#if (hasRequestBody this)}}
|
|
114
|
+
if ($input !== null) {
|
|
115
|
+
$requestDef['body'] = $input;
|
|
116
|
+
}
|
|
117
|
+
{{/if}}
|
|
118
|
+
|
|
119
|
+
$response = $this->transport->executeRequest($requestDef);
|
|
120
|
+
|
|
121
|
+
// Deserialize response
|
|
122
|
+
$data = $this->decodeResponse($response);
|
|
123
|
+
|
|
124
|
+
{{#if (eq responseType "void")}}
|
|
125
|
+
return;
|
|
126
|
+
{{else if (eq responseType "string")}}
|
|
127
|
+
return (string) $data;
|
|
128
|
+
{{else if (eq responseType "int")}}
|
|
129
|
+
return (int) $data;
|
|
130
|
+
{{else if (eq responseType "float")}}
|
|
131
|
+
return (float) $data;
|
|
132
|
+
{{else if (eq responseType "bool")}}
|
|
133
|
+
return (bool) $data;
|
|
134
|
+
{{else if (eq responseType "array")}}
|
|
135
|
+
return $data;
|
|
136
|
+
{{else}}
|
|
137
|
+
// Complex type: deserialize via fromArray
|
|
138
|
+
if (class_exists({{responseType}}::class) && method_exists({{responseType}}::class, 'fromArray')) {
|
|
139
|
+
return {{responseType}}::fromArray($data);
|
|
140
|
+
}
|
|
141
|
+
return $data;
|
|
142
|
+
{{/if}}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
{{/if}}
|
|
146
|
+
{{/each}}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Decode a PSR-7 response body to an associative array or raw value.
|
|
150
|
+
*
|
|
151
|
+
* @return array<string, mixed>|string
|
|
152
|
+
*/
|
|
153
|
+
private function decodeResponse(ResponseInterface $response): array|string
|
|
154
|
+
{
|
|
155
|
+
$contents = $response->getBody()->__toString();
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
$decoded = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
|
|
159
|
+
if (is_array($decoded)) {
|
|
160
|
+
return $decoded;
|
|
161
|
+
}
|
|
162
|
+
if (is_scalar($decoded)) {
|
|
163
|
+
return (string) $decoded;
|
|
164
|
+
}
|
|
165
|
+
return ['raw' => $decoded];
|
|
166
|
+
} catch (\JsonException) {
|
|
167
|
+
if (trim($contents) === '') {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
return $contents;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -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
|
+
}
|