@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.
Files changed (114) hide show
  1. package/dist/adapters/csharp/index.d.ts +12 -0
  2. package/dist/adapters/csharp/index.d.ts.map +1 -0
  3. package/dist/adapters/csharp/index.js +99 -0
  4. package/dist/adapters/csharp/index.js.map +1 -0
  5. package/dist/adapters/go/index.d.ts +12 -0
  6. package/dist/adapters/go/index.d.ts.map +1 -0
  7. package/dist/adapters/go/index.js +135 -0
  8. package/dist/adapters/go/index.js.map +1 -0
  9. package/dist/adapters/kotlin/index.d.ts +13 -0
  10. package/dist/adapters/kotlin/index.d.ts.map +1 -0
  11. package/dist/adapters/kotlin/index.js +188 -0
  12. package/dist/adapters/kotlin/index.js.map +1 -0
  13. package/dist/adapters/php/index.d.ts +20 -0
  14. package/dist/adapters/php/index.d.ts.map +1 -0
  15. package/dist/adapters/php/index.js +123 -0
  16. package/dist/adapters/php/index.js.map +1 -0
  17. package/dist/adapters/python/index.d.ts +12 -0
  18. package/dist/adapters/python/index.d.ts.map +1 -0
  19. package/dist/adapters/python/index.js +145 -0
  20. package/dist/adapters/python/index.js.map +1 -0
  21. package/dist/adapters/ruby/index.d.ts +12 -0
  22. package/dist/adapters/ruby/index.d.ts.map +1 -0
  23. package/dist/adapters/ruby/index.js +86 -0
  24. package/dist/adapters/ruby/index.js.map +1 -0
  25. package/dist/adapters/rust/index.d.ts +13 -0
  26. package/dist/adapters/rust/index.d.ts.map +1 -0
  27. package/dist/adapters/rust/index.js +213 -0
  28. package/dist/adapters/rust/index.js.map +1 -0
  29. package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
  30. package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
  31. package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
  32. package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
  33. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
  34. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
  35. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +5 -3
  36. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
  37. package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
  38. package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
  39. package/dist/adapters/typescript/index.d.ts.map +1 -1
  40. package/dist/adapters/typescript/index.js +43 -2
  41. package/dist/adapters/typescript/index.js.map +1 -1
  42. package/dist/cli.js +2 -1
  43. package/dist/cli.js.map +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +14 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/pipeline/intake.d.ts.map +1 -1
  48. package/dist/pipeline/intake.js +32 -21
  49. package/dist/pipeline/intake.js.map +1 -1
  50. package/dist/types/ir.d.ts +1 -1
  51. package/dist/types/ir.d.ts.map +1 -1
  52. package/dist/types/sdk-module.d.ts +22 -2
  53. package/dist/types/sdk-module.d.ts.map +1 -1
  54. package/dist/types/sdk-module.js +1 -1
  55. package/package.json +1 -1
  56. package/templates/csharp/client.hbs +95 -0
  57. package/templates/csharp/csproj.hbs +18 -0
  58. package/templates/csharp/domain.hbs +183 -0
  59. package/templates/csharp/errors.hbs +45 -0
  60. package/templates/csharp/readme.hbs +76 -0
  61. package/templates/csharp/transport.hbs +291 -0
  62. package/templates/csharp/types.hbs +104 -0
  63. package/templates/go/client.hbs +158 -0
  64. package/templates/go/domain.hbs +89 -0
  65. package/templates/go/errors.hbs +38 -0
  66. package/templates/go/go-mod.hbs +6 -0
  67. package/templates/go/readme.hbs +73 -0
  68. package/templates/go/transport.hbs +379 -0
  69. package/templates/go/types.hbs +80 -0
  70. package/templates/go/utils.hbs +45 -0
  71. package/templates/kotlin/build-gradle.hbs +151 -0
  72. package/templates/kotlin/client.hbs +57 -0
  73. package/templates/kotlin/domain.hbs +73 -0
  74. package/templates/kotlin/errors.hbs +42 -0
  75. package/templates/kotlin/gradle-properties.hbs +2 -0
  76. package/templates/kotlin/readme.hbs +133 -0
  77. package/templates/kotlin/settings-gradle.hbs +1 -0
  78. package/templates/kotlin/transport.hbs +246 -0
  79. package/templates/kotlin/types.hbs +100 -0
  80. package/templates/php/client.hbs +74 -0
  81. package/templates/php/composer.hbs +19 -0
  82. package/templates/php/domain.hbs +173 -0
  83. package/templates/php/errors.hbs +131 -0
  84. package/templates/php/readme.hbs +89 -0
  85. package/templates/php/transport.hbs +285 -0
  86. package/templates/php/types.hbs +95 -0
  87. package/templates/python/client.hbs +102 -0
  88. package/templates/python/domain.hbs +139 -0
  89. package/templates/python/index.hbs +20 -0
  90. package/templates/python/pyproject.hbs +26 -0
  91. package/templates/python/readme.hbs +64 -0
  92. package/templates/python/resources-init.hbs +11 -0
  93. package/templates/python/transport.hbs +191 -0
  94. package/templates/python/types-errors.hbs +75 -0
  95. package/templates/python/types.hbs +79 -0
  96. package/templates/ruby/Gemfile.hbs +5 -0
  97. package/templates/ruby/client.hbs +86 -0
  98. package/templates/ruby/domain.hbs +110 -0
  99. package/templates/ruby/errors.hbs +150 -0
  100. package/templates/ruby/gemspec.hbs +30 -0
  101. package/templates/ruby/index.hbs +54 -0
  102. package/templates/ruby/readme.hbs +113 -0
  103. package/templates/ruby/transport.hbs +177 -0
  104. package/templates/ruby/types.hbs +126 -0
  105. package/templates/ruby/version.hbs +5 -0
  106. package/templates/rust/api-mod.hbs +3 -0
  107. package/templates/rust/cargo.hbs +22 -0
  108. package/templates/rust/client.hbs +114 -0
  109. package/templates/rust/domain.hbs +94 -0
  110. package/templates/rust/error.hbs +74 -0
  111. package/templates/rust/lib.hbs +85 -0
  112. package/templates/rust/readme.hbs +77 -0
  113. package/templates/rust/transport.hbs +287 -0
  114. package/templates/rust/types.hbs +142 -0
@@ -0,0 +1,246 @@
1
+ package {{packageName}}
2
+
3
+ import io.ktor.client.*
4
+ import io.ktor.client.engine.cio.*
5
+ import io.ktor.client.plugins.*
6
+ import io.ktor.client.plugins.contentnegotiation.*
7
+ import io.ktor.client.request.*
8
+ import io.ktor.client.statement.*
9
+ import io.ktor.http.*
10
+ import io.ktor.serialization.kotlinx.json.*
11
+ import kotlinx.serialization.json.Json
12
+ import kotlinx.serialization.json.JsonObject
13
+ import kotlinx.serialization.json.jsonPrimitive
14
+ import kotlinx.coroutines.*
15
+
16
+ /**
17
+ * Internal transport configuration.
18
+ */
19
+ data class TransportConfig(
20
+ val baseUrl: String,
21
+ {{#each securitySchemes}}
22
+ val {{configKey}}: String? = null,
23
+ {{/each}}
24
+ val timeoutMs: Long = 15000,
25
+ val maxRetries: Int = 3,
26
+ )
27
+
28
+ /**
29
+ * HTTP transport layer for the SDK.
30
+ *
31
+ * Handles request building, authentication, retries, and error mapping
32
+ * using Ktor's HttpClient.
33
+ */
34
+ class Transport(
35
+ private val config: TransportConfig
36
+ ) {
37
+ private val json = Json {
38
+ ignoreUnknownKeys = true
39
+ isLenient = true
40
+ encodeDefaults = true
41
+ coerceInputValues = true
42
+ }
43
+
44
+ private val client = HttpClient(CIO) {
45
+ install(ContentNegotiation) {
46
+ json(json)
47
+ }
48
+
49
+ install(HttpTimeout) {
50
+ requestTimeoutMillis = config.timeoutMs
51
+ connectTimeoutMillis = config.timeoutMs
52
+ socketTimeoutMillis = config.timeoutMs
53
+ }
54
+
55
+ defaultRequest {
56
+ url(config.baseUrl)
57
+ contentType(ContentType.Application.Json)
58
+ accept(ContentType.Application.Json)
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Execute an HTTP request, deserializing the response body as JSON.
64
+ */
65
+ suspend inline fun <reified T> execute(
66
+ method: HttpMethod,
67
+ path: String,
68
+ query: Map<String, String?>? = null,
69
+ body: Any? = null,
70
+ headers: Map<String, String>? = null,
71
+ ): T {
72
+ val response = executeRaw(method, path, query, body, headers)
73
+ return json.decodeFromString<T>(response)
74
+ }
75
+
76
+ /**
77
+ * Execute an HTTP request, returning the raw response body as a string.
78
+ */
79
+ suspend fun executeRaw(
80
+ method: HttpMethod,
81
+ path: String,
82
+ query: Map<String, String?>? = null,
83
+ body: Any? = null,
84
+ headers: Map<String, String>? = null,
85
+ ): String {
86
+ return executeWithRetry(method, path, query, body, headers)
87
+ }
88
+
89
+ /**
90
+ * Close the underlying HTTP client. Call this when the SDK is no longer needed.
91
+ */
92
+ fun close() {
93
+ client.close()
94
+ }
95
+
96
+ // ── private helpers ─────────────────────────────────────────────────────
97
+
98
+ @OptIn(ExperimentalCoroutinesApi::class)
99
+ private suspend fun executeWithRetry(
100
+ method: HttpMethod,
101
+ path: String,
102
+ query: Map<String, String?>? = null,
103
+ body: Any? = null,
104
+ headers: Map<String, String>? = null,
105
+ attempt: Int = 0,
106
+ ): String {
107
+ return try {
108
+ val response = client.request {
109
+ this.method = method
110
+ url {
111
+ // Base URL is set as default; append the path
112
+ val fullUrl = "${config.baseUrl.trimEnd('/')}${path}"
113
+ takeFrom(fullUrl)
114
+
115
+ // Add query parameters
116
+ query?.forEach { (key, value) ->
117
+ if (value != null) {
118
+ parameters.append(key, value)
119
+ }
120
+ }
121
+ }
122
+
123
+ // Set request body
124
+ if (body != null) {
125
+ setBody(body)
126
+ }
127
+
128
+ // Set custom headers
129
+ headers?.forEach { (key, value) ->
130
+ this.headers.append(key, value)
131
+ }
132
+
133
+ // Apply authentication
134
+ applyAuth()
135
+ }
136
+
137
+ val status = response.status.value
138
+
139
+ if (status in 200..299) {
140
+ response.bodyAsText()
141
+ } else if ((status >= 500 || status == 429) && attempt < config.maxRetries) {
142
+ // Retry on server errors and rate limiting
143
+ val backoffMs = 200L * (1L shl attempt)
144
+ delay(backoffMs)
145
+ executeWithRetry(method, path, query, body, headers, attempt + 1)
146
+ } else {
147
+ throw buildSdkException(response)
148
+ }
149
+ } catch (e: SdkException) {
150
+ throw e
151
+ } catch (e: CancellationException) {
152
+ throw e
153
+ } catch (e: Exception) {
154
+ if (attempt < config.maxRetries) {
155
+ val backoffMs = 200L * (1L shl attempt)
156
+ delay(backoffMs)
157
+ executeWithRetry(method, path, query, body, headers, attempt + 1)
158
+ } else {
159
+ throw SdkException(
160
+ status = null,
161
+ code = null,
162
+ message = "Network error: ${e.message}",
163
+ requestId = null,
164
+ cause = e,
165
+ )
166
+ }
167
+ }
168
+ }
169
+
170
+ private suspend fun buildSdkException(response: HttpResponse): SdkException {
171
+ val status = response.status.value
172
+ val body = try {
173
+ response.bodyAsText()
174
+ } catch (e: Exception) {
175
+ null
176
+ }
177
+
178
+ val requestId = response.headers["x-request-id"]
179
+ ?: response.headers["request-id"]
180
+
181
+ // Try to extract structured error info from the JSON body
182
+ var code: String? = null
183
+ var message: String = body ?: "API error (status $status)"
184
+
185
+ if (body != null) {
186
+ try {
187
+ val jsonBody = json.decodeFromString<JsonObject>(body)
188
+ code = jsonBody["code"]?.jsonPrimitive?.content
189
+ ?: jsonBody["error"]?.let { err ->
190
+ when (err) {
191
+ is JsonObject -> err["code"]?.jsonPrimitive?.content
192
+ else -> err.jsonPrimitive?.content
193
+ }
194
+ }
195
+ message = jsonBody["message"]?.jsonPrimitive?.content
196
+ ?: jsonBody["error"]?.let { err ->
197
+ when (err) {
198
+ is JsonObject -> err["message"]?.jsonPrimitive?.content
199
+ else -> err.jsonPrimitive?.content
200
+ }
201
+ }
202
+ ?: message
203
+ } catch (_: Exception) {
204
+ // Use raw body as the message
205
+ message = body
206
+ }
207
+ }
208
+
209
+ return SdkException(
210
+ status = status,
211
+ code = code,
212
+ message = message,
213
+ requestId = requestId,
214
+ )
215
+ }
216
+
217
+ private fun HttpRequestBuilder.applyAuth() {
218
+ {{#each securitySchemes}}
219
+ {{#if (eq type "apiKey")}}
220
+ {{#if (eq in "header")}}
221
+ config.{{configKey}}?.let { key ->
222
+ headers.append("{{paramName}}", key)
223
+ }
224
+ {{/if}}
225
+ {{#if (eq in "query")}}
226
+ // API key via query parameter handled during URL construction
227
+ {{/if}}
228
+ {{/if}}
229
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
230
+ config.{{configKey}}?.let { token ->
231
+ headers.append("Authorization", "Bearer $token")
232
+ }
233
+ {{/if}}
234
+ {{#if (eq type "oauth2")}}
235
+ config.{{configKey}}?.let { token ->
236
+ headers.append("Authorization", "Bearer $token")
237
+ }
238
+ {{/if}}
239
+ {{#if (eq type "openIdConnect")}}
240
+ config.{{configKey}}?.let { token ->
241
+ headers.append("Authorization", "Bearer $token")
242
+ }
243
+ {{/if}}
244
+ {{/each}}
245
+ }
246
+ }
@@ -0,0 +1,100 @@
1
+ package {{packageName}}
2
+
3
+ import kotlinx.serialization.*
4
+ import kotlinx.serialization.descriptors.*
5
+ import kotlinx.serialization.encoding.*
6
+ import kotlinx.serialization.json.*
7
+
8
+ {{#each schemas}}
9
+ {{#if (not ownership)}}
10
+ {{#unless (or externalImport (eq ownership.kind "external"))}}
11
+ {{#if (not isEnum)}}
12
+ {{#if (not isUnionType)}}
13
+ {{#if (not isIntersectionType)}}
14
+ {{#if (not isTypeAlias)}}
15
+ {{#if description}}
16
+ /**
17
+ * {{description}}
18
+ */
19
+ {{/if}}
20
+ @Serializable
21
+ data class {{name}}(
22
+ {{#each properties}}
23
+ {{#if description}}
24
+ /** {{description}} */
25
+ {{/if}}
26
+ {{#if deprecated}}
27
+ @Deprecated("{{#if deprecationMessage}}{{deprecationMessage}}{{else}}Use a different property.{{/if}}")
28
+ {{/if}}
29
+ {{#if (and nullable (not required))}}
30
+ @SerialName("{{name}}")
31
+ val {{name}}: {{{kotlinType type}}}? = null,
32
+ {{else if nullable}}
33
+ @SerialName("{{name}}")
34
+ val {{name}}: {{{kotlinType type}}}? = null,
35
+ {{else if required}}
36
+ @SerialName("{{name}}")
37
+ val {{name}}: {{{kotlinType type}}},
38
+ {{else}}
39
+ @SerialName("{{name}}")
40
+ val {{name}}: {{{kotlinType type}}}? = null,
41
+ {{/if}}
42
+ {{/each}}
43
+ )
44
+ {{/if}}
45
+ {{/if}}
46
+ {{/if}}
47
+ {{/if}}
48
+ {{/unless}}
49
+ {{/if}}
50
+
51
+ {{#if isEnum}}
52
+ {{#if description}}
53
+ /**
54
+ * {{description}}
55
+ */
56
+ {{/if}}
57
+ @Serializable
58
+ enum class {{name}} {
59
+ {{#each enumValues}}
60
+ @SerialName("{{this}}")
61
+ {{pascalCase this}},
62
+ {{/each}}
63
+ }
64
+ {{/if}}
65
+
66
+ {{#if isUnionType}}
67
+ {{#if unionMembers.length}}
68
+ {{#if description}}
69
+ /**
70
+ * {{description}}
71
+ */
72
+ {{/if}}
73
+ @Serializable
74
+ sealed class {{name}} {
75
+ {{#each unionMembers}}
76
+ @Serializable
77
+ data class {{pascalCase this}}(val value: {{{kotlinType this}}}) : {{../name}}()
78
+ {{/each}}
79
+ }
80
+ {{/if}}
81
+ {{/if}}
82
+
83
+ {{#if isIntersectionType}}
84
+ {{#if intersectionMembers.length}}
85
+ {{#if description}}
86
+ /**
87
+ * {{description}}
88
+ */
89
+ {{/if}}
90
+ {{! Intersection type — emit as interface implemented by concrete types }}
91
+ @Serializable
92
+ data class {{name}}(
93
+ // Intersection of: {{join intersectionMembers ", "}}
94
+ // Fields from all intersected types should be inlined here.
95
+ // TODO: generate combined fields
96
+ )
97
+ {{/if}}
98
+ {{/if}}
99
+
100
+ {{/each}}
@@ -0,0 +1,74 @@
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 groups}}
14
+ use {{namespace}}\Api\{{className}};
15
+ {{/each}}
16
+
17
+ /**
18
+ * {{title}} SDK Client.
19
+ *
20
+ * Entry point for all API operations. Provides accessor methods
21
+ * for each domain group and manages shared transport configuration.
22
+ *
23
+ * Usage:
24
+ * <code>
25
+ * $client = new Client(['baseUrl' => '{{baseUrl}}']);
26
+ * $result = $client->{{groups.[0].name}}()->{{groups.[0].operations.[0].name}}(...);
27
+ * </code>
28
+ */
29
+ class Client
30
+ {
31
+ private Transport $transport;
32
+
33
+ /**
34
+ * @param array{
35
+ * baseUrl?: string,
36
+ * timeoutMs?: int,
37
+ * retries?: array{attempts?: int, backoffMs?: int, jitter?: bool, maxBackoffMs?: int},
38
+ * defaultHeaders?: array<string, string>,
39
+ {{#each securitySchemes}}
40
+ * {{configKey}}?: string,
41
+ {{/each}}
42
+ * } $config
43
+ */
44
+ public function __construct(array $config = [])
45
+ {
46
+ $this->transport = new Transport($config);
47
+ }
48
+
49
+ /**
50
+ * Get the underlying transport instance for advanced use.
51
+ */
52
+ public function getTransport(): Transport
53
+ {
54
+ return $this->transport;
55
+ }
56
+
57
+ {{#each groups}}
58
+ /** @var {{className}}|null */
59
+ private ?{{className}} $_{{{name}}} = null;
60
+
61
+ /**
62
+ {{#if description}}
63
+ * {{description}}
64
+ *
65
+ {{/if}}
66
+ * @return {{className}}
67
+ */
68
+ public function {{name}}(): {{className}}
69
+ {
70
+ return $this->_{{{name}}} ??= new {{className}}($this->transport);
71
+ }
72
+
73
+ {{/each}}
74
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{composerName}}",
3
+ "description": "{{#if description}}{{description}}{{else}}PHP SDK for {{title}}{{/if}}",
4
+ "type": "library",
5
+ "license": "MIT",
6
+ "autoload": {
7
+ "psr-4": {
8
+ "{{namespace}}\\": "src/"
9
+ }
10
+ },
11
+ "require": {
12
+ "php": ">=8.1",
13
+ "guzzlehttp/guzzle": "^7.0",
14
+ "ext-json": "*"
15
+ },
16
+ "require-dev": {
17
+ "phpunit/phpunit": "^10.0"
18
+ }
19
+ }
@@ -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
+ }