@apollo-deploy/tesseract 1.9.0 → 2.1.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/README.md +218 -110
- package/dist/adapters/csharp/index.d.ts +5 -8
- package/dist/adapters/csharp/index.d.ts.map +1 -1
- package/dist/adapters/csharp/index.js +314 -74
- package/dist/adapters/csharp/index.js.map +1 -1
- package/dist/adapters/documentation.d.ts +17 -0
- package/dist/adapters/documentation.d.ts.map +1 -0
- package/dist/adapters/documentation.js +268 -0
- package/dist/adapters/documentation.js.map +1 -0
- package/dist/adapters/go/index.d.ts +5 -8
- package/dist/adapters/go/index.d.ts.map +1 -1
- package/dist/adapters/go/index.js +197 -101
- package/dist/adapters/go/index.js.map +1 -1
- package/dist/adapters/kotlin/index.d.ts +5 -9
- package/dist/adapters/kotlin/index.d.ts.map +1 -1
- package/dist/adapters/kotlin/index.js +280 -212
- package/dist/adapters/kotlin/index.js.map +1 -1
- package/dist/adapters/php/index.d.ts +5 -15
- package/dist/adapters/php/index.d.ts.map +1 -1
- package/dist/adapters/php/index.js +362 -90
- package/dist/adapters/php/index.js.map +1 -1
- package/dist/adapters/python/index.d.ts +5 -8
- package/dist/adapters/python/index.d.ts.map +1 -1
- package/dist/adapters/python/index.js +240 -109
- package/dist/adapters/python/index.js.map +1 -1
- package/dist/adapters/ruby/index.d.ts +3 -3
- package/dist/adapters/ruby/index.d.ts.map +1 -1
- package/dist/adapters/ruby/index.js +235 -46
- package/dist/adapters/ruby/index.js.map +1 -1
- package/dist/adapters/rust/index.d.ts +5 -9
- package/dist/adapters/rust/index.d.ts.map +1 -1
- package/dist/adapters/rust/index.js +237 -169
- package/dist/adapters/rust/index.js.map +1 -1
- package/dist/adapters/shared.d.ts +7 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +68 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/type-utils.d.ts +13 -0
- package/dist/adapters/type-utils.d.ts.map +1 -0
- package/dist/adapters/type-utils.js +105 -0
- package/dist/adapters/type-utils.js.map +1 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.d.ts +2 -2
- package/dist/adapters/typescript/emitter/emitSchema.js +39 -27
- 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.js +15 -15
- package/dist/adapters/typescript/emitter/importCollector.d.ts +2 -4
- package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/importCollector.js +40 -50
- package/dist/adapters/typescript/emitter/importCollector.js.map +1 -1
- package/dist/adapters/typescript/emitter/parsers.d.ts +1 -1
- package/dist/adapters/typescript/emitter/parsers.js +13 -13
- package/dist/adapters/typescript/emitter/typeHoister.d.ts +1 -1
- package/dist/adapters/typescript/emitter/typeHoister.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/typeHoister.js +12 -20
- package/dist/adapters/typescript/emitter/typeHoister.js.map +1 -1
- package/dist/adapters/typescript/index.d.ts +3 -3
- package/dist/adapters/typescript/index.d.ts.map +1 -1
- package/dist/adapters/typescript/index.js +148 -166
- package/dist/adapters/typescript/index.js.map +1 -1
- package/dist/adapters/typescript/shared.d.ts +2 -2
- package/dist/adapters/typescript/shared.js +12 -12
- package/dist/cli.js +50 -37
- package/dist/cli.js.map +1 -1
- package/dist/collector.d.ts +32 -7
- package/dist/collector.d.ts.map +1 -1
- package/dist/collector.js +149 -49
- package/dist/collector.js.map +1 -1
- package/dist/elysia.js +2 -2
- package/dist/elysia.js.map +1 -1
- package/dist/express.d.ts.map +1 -1
- package/dist/express.js.map +1 -1
- package/dist/fastify.d.ts +9 -2
- package/dist/fastify.d.ts.map +1 -1
- package/dist/fastify.js +40 -21
- package/dist/fastify.js.map +1 -1
- package/dist/helpers/handlebars.d.ts +7 -16
- package/dist/helpers/handlebars.d.ts.map +1 -1
- package/dist/helpers/handlebars.js +127 -177
- package/dist/helpers/handlebars.js.map +1 -1
- package/dist/hono.d.ts.map +1 -1
- package/dist/hono.js.map +1 -1
- package/dist/index.d.ts +14 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -30
- package/dist/index.js.map +1 -1
- package/dist/koa.d.ts.map +1 -1
- package/dist/koa.js.map +1 -1
- package/dist/nestjs.d.ts.map +1 -1
- package/dist/nestjs.js +41 -14
- package/dist/nestjs.js.map +1 -1
- package/dist/pipeline/enrich.d.ts +2 -13
- package/dist/pipeline/enrich.d.ts.map +1 -1
- package/dist/pipeline/enrich.js +157 -212
- package/dist/pipeline/enrich.js.map +1 -1
- package/dist/pipeline/intake.d.ts +3 -3
- package/dist/pipeline/intake.d.ts.map +1 -1
- package/dist/pipeline/intake.js +342 -238
- package/dist/pipeline/intake.js.map +1 -1
- package/dist/pipeline/write.d.ts +1 -14
- package/dist/pipeline/write.d.ts.map +1 -1
- package/dist/pipeline/write.js +73 -113
- package/dist/pipeline/write.js.map +1 -1
- package/dist/types/config.d.ts +14 -26
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +16 -12
- package/dist/types/config.js.map +1 -1
- package/dist/types/ir.d.ts +12 -8
- package/dist/types/ir.d.ts.map +1 -1
- package/dist/types/manifest.d.ts +10 -11
- package/dist/types/manifest.d.ts.map +1 -1
- package/dist/types/manifest.js +54 -14
- package/dist/types/manifest.js.map +1 -1
- package/dist/types/sdk-module.d.ts +2 -2
- package/dist/types/sdk-module.js +1 -1
- package/dist/utils/format.d.ts +1 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +3 -10
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/naming.d.ts.map +1 -1
- package/dist/utils/naming.js +174 -184
- package/dist/utils/naming.js.map +1 -1
- package/dist/utils/npm-token.d.ts +1 -17
- package/dist/utils/npm-token.d.ts.map +1 -1
- package/dist/utils/npm-token.js +27 -76
- package/dist/utils/npm-token.js.map +1 -1
- package/dist/utils/version.d.ts +14 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +60 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +4 -3
- package/templates/csharp/client.hbs +26 -49
- package/templates/csharp/csproj.hbs +7 -0
- package/templates/csharp/domain.hbs +48 -130
- package/templates/csharp/errors.hbs +17 -25
- package/templates/csharp/readme.hbs +31 -44
- package/templates/csharp/transport.hbs +238 -136
- package/templates/csharp/types.hbs +30 -76
- package/templates/go/client.hbs +44 -109
- package/templates/go/domain.hbs +83 -25
- package/templates/go/go-mod.hbs +1 -4
- package/templates/go/readme.hbs +32 -29
- package/templates/go/transport.hbs +174 -173
- package/templates/go/types.hbs +17 -53
- package/templates/go/utils.hbs +3 -2
- package/templates/kotlin/build-gradle.hbs +1 -1
- package/templates/kotlin/client.hbs +18 -15
- package/templates/kotlin/domain.hbs +54 -51
- package/templates/kotlin/errors.hbs +1 -1
- package/templates/kotlin/readme.hbs +35 -137
- package/templates/kotlin/transport.hbs +140 -123
- package/templates/kotlin/types.hbs +30 -81
- package/templates/php/client.hbs +8 -46
- package/templates/php/composer.hbs +2 -1
- package/templates/php/domain.hbs +86 -117
- package/templates/php/errors.hbs +1 -1
- package/templates/php/readme.hbs +42 -49
- package/templates/php/transport.hbs +192 -145
- package/templates/php/types.hbs +63 -60
- package/templates/python/client.hbs +15 -2
- package/templates/python/domain.hbs +50 -108
- package/templates/python/pyproject.hbs +5 -0
- package/templates/python/readme.hbs +47 -30
- package/templates/python/resources-init.hbs +1 -1
- package/templates/python/transport.hbs +167 -53
- package/templates/python/types.hbs +22 -58
- package/templates/ruby/client.hbs +10 -6
- package/templates/ruby/domain.hbs +48 -67
- package/templates/ruby/errors.hbs +3 -3
- package/templates/ruby/gemspec.hbs +3 -10
- package/templates/ruby/index.hbs +3 -3
- package/templates/ruby/readme.hbs +33 -70
- package/templates/ruby/transport.hbs +129 -103
- package/templates/ruby/types.hbs +11 -16
- package/templates/ruby/version.hbs +1 -1
- package/templates/rust/cargo.hbs +2 -0
- package/templates/rust/client.hbs +10 -1
- package/templates/rust/domain.hbs +69 -52
- package/templates/rust/error.hbs +3 -1
- package/templates/rust/lib.hbs +4 -5
- package/templates/rust/readme.hbs +33 -46
- package/templates/rust/transport.hbs +170 -159
- package/templates/rust/types.hbs +34 -111
- package/templates/typescript/domain-class.hbs +14 -6
- package/templates/typescript/domain.hbs +13 -5
- package/templates/typescript/package-json.hbs +3 -1
- package/templates/typescript/readme.hbs +42 -42
- package/templates/typescript/transport.hbs +46 -8
- package/templates/typescript/types-errors.hbs +1 -1
- package/dist/adapters/typescript/model-emitter.d.ts +0 -13
- package/dist/adapters/typescript/model-emitter.d.ts.map +0 -1
- package/dist/adapters/typescript/model-emitter.js +0 -415
- package/dist/adapters/typescript/model-emitter.js.map +0 -1
|
@@ -9,34 +9,45 @@ import io.ktor.client.request.*
|
|
|
9
9
|
import io.ktor.client.statement.*
|
|
10
10
|
import io.ktor.http.*
|
|
11
11
|
import io.ktor.serialization.kotlinx.json.*
|
|
12
|
+
import java.time.ZonedDateTime
|
|
13
|
+
import java.time.format.DateTimeFormatter
|
|
14
|
+
import kotlin.math.max
|
|
15
|
+
import kotlin.random.Random
|
|
16
|
+
import kotlinx.coroutines.CancellationException
|
|
17
|
+
import kotlinx.coroutines.delay
|
|
12
18
|
import kotlinx.serialization.json.Json
|
|
19
|
+
import kotlinx.serialization.json.JsonElement
|
|
13
20
|
import kotlinx.serialization.json.JsonObject
|
|
14
21
|
import kotlinx.serialization.json.jsonPrimitive
|
|
15
|
-
import kotlinx.coroutines.*
|
|
16
22
|
|
|
17
|
-
/**
|
|
18
|
-
* Internal transport configuration.
|
|
19
|
-
*/
|
|
23
|
+
/** Internal transport configuration. */
|
|
20
24
|
data class TransportConfig(
|
|
21
25
|
val baseUrl: String,
|
|
22
26
|
{{#each securitySchemes}}
|
|
23
27
|
val {{configKey}}: String? = null,
|
|
24
28
|
{{/each}}
|
|
25
29
|
val defaultHeaders: Map<String, String> = emptyMap(),
|
|
26
|
-
val
|
|
27
|
-
val timeoutMs: Long = 15000,
|
|
30
|
+
val timeoutMs: Long = 15_000,
|
|
28
31
|
val maxRetries: Int = 3,
|
|
32
|
+
val retryUnsafeRequests: Boolean = false,
|
|
29
33
|
)
|
|
30
34
|
|
|
31
35
|
/**
|
|
32
36
|
* HTTP transport layer for the SDK.
|
|
33
37
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
38
|
+
* Retries transient failures for idempotent methods. POST and PATCH are retried
|
|
39
|
+
* only when [TransportConfig.retryUnsafeRequests] is enabled or an
|
|
40
|
+
* `X-Idempotency-Key` header is present.
|
|
36
41
|
*/
|
|
37
42
|
class Transport(
|
|
38
|
-
private val config: TransportConfig
|
|
39
|
-
) {
|
|
43
|
+
private val config: TransportConfig,
|
|
44
|
+
) : AutoCloseable {
|
|
45
|
+
init {
|
|
46
|
+
require(config.baseUrl.isNotBlank()) { "baseUrl must not be blank" }
|
|
47
|
+
require(config.timeoutMs > 0) { "timeoutMs must be greater than zero" }
|
|
48
|
+
require(config.maxRetries >= 0) { "maxRetries must not be negative" }
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
@PublishedApi
|
|
41
52
|
internal val json = Json {
|
|
42
53
|
ignoreUnknownKeys = true
|
|
@@ -58,8 +69,10 @@ class Transport(
|
|
|
58
69
|
|
|
59
70
|
defaultRequest {
|
|
60
71
|
url(config.baseUrl)
|
|
61
|
-
contentType(ContentType.Application.Json)
|
|
62
72
|
accept(ContentType.Application.Json)
|
|
73
|
+
if (config.defaultHeaders.keys.none { it.equals(HttpHeaders.UserAgent, ignoreCase = true) }) {
|
|
74
|
+
header(HttpHeaders.UserAgent, "{{packageName}}/{{packageVersion}}")
|
|
75
|
+
}
|
|
63
76
|
{{#each securitySchemes}}
|
|
64
77
|
{{#if (eq type "apiKey")}}
|
|
65
78
|
{{#if (eq in "header")}}
|
|
@@ -67,169 +80,173 @@ class Transport(
|
|
|
67
80
|
{{/if}}
|
|
68
81
|
{{/if}}
|
|
69
82
|
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
70
|
-
config.{{configKey}}?.let { token -> headers.append(
|
|
83
|
+
config.{{configKey}}?.let { token -> headers.append(HttpHeaders.Authorization, "Bearer $token") }
|
|
71
84
|
{{/if}}
|
|
72
85
|
{{#if (eq type "oauth2")}}
|
|
73
|
-
config.{{configKey}}?.let { token -> headers.append(
|
|
86
|
+
config.{{configKey}}?.let { token -> headers.append(HttpHeaders.Authorization, "Bearer $token") }
|
|
74
87
|
{{/if}}
|
|
75
88
|
{{#if (eq type "openIdConnect")}}
|
|
76
|
-
config.{{configKey}}?.let { token -> headers.append(
|
|
89
|
+
config.{{configKey}}?.let { token -> headers.append(HttpHeaders.Authorization, "Bearer $token") }
|
|
77
90
|
{{/if}}
|
|
78
91
|
{{/each}}
|
|
79
|
-
config.defaultHeaders.forEach { (
|
|
92
|
+
config.defaultHeaders.forEach { (key, value) -> headers.append(key, value) }
|
|
80
93
|
}
|
|
81
94
|
}
|
|
82
95
|
|
|
83
|
-
/**
|
|
84
|
-
* Execute an HTTP request, deserializing the response body as JSON.
|
|
85
|
-
*/
|
|
96
|
+
/** Execute an HTTP request and deserialize its JSON response. */
|
|
86
97
|
suspend inline fun <reified T> execute(
|
|
87
98
|
method: HttpMethod,
|
|
88
99
|
path: String,
|
|
89
100
|
query: Map<String, String?>? = null,
|
|
90
|
-
body:
|
|
101
|
+
body: JsonElement? = null,
|
|
91
102
|
headers: Map<String, String>? = null,
|
|
92
103
|
): T {
|
|
93
104
|
val response = executeRaw(method, path, query, body, headers)
|
|
105
|
+
if (response.isBlank()) {
|
|
106
|
+
@Suppress("UNCHECKED_CAST")
|
|
107
|
+
return Unit as T
|
|
108
|
+
}
|
|
94
109
|
return json.decodeFromString<T>(response)
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
/**
|
|
98
|
-
* Execute an HTTP request, returning the raw response body as a string.
|
|
99
|
-
*/
|
|
112
|
+
/** Execute an HTTP request and return its raw response body. */
|
|
100
113
|
suspend fun executeRaw(
|
|
101
114
|
method: HttpMethod,
|
|
102
115
|
path: String,
|
|
103
116
|
query: Map<String, String?>? = null,
|
|
104
|
-
body:
|
|
117
|
+
body: JsonElement? = null,
|
|
105
118
|
headers: Map<String, String>? = null,
|
|
106
119
|
): String {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Close the underlying HTTP client. Call this when the SDK is no longer needed.
|
|
112
|
-
*/
|
|
113
|
-
fun close() {
|
|
114
|
-
client.close()
|
|
115
|
-
}
|
|
120
|
+
val canRetry = canRetry(method, headers)
|
|
121
|
+
var lastError: Throwable? = null
|
|
116
122
|
|
|
117
|
-
|
|
123
|
+
for (attempt in 0..config.maxRetries) {
|
|
124
|
+
try {
|
|
125
|
+
val response = client.request {
|
|
126
|
+
this.method = method
|
|
127
|
+
url {
|
|
128
|
+
takeFrom("${config.baseUrl.trimEnd('/')}$path")
|
|
129
|
+
query?.forEach { (key, value) ->
|
|
130
|
+
value?.let { parameters.append(key, it) }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
118
133
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
path: String,
|
|
123
|
-
query: Map<String, String?>? = null,
|
|
124
|
-
body: Any? = null,
|
|
125
|
-
headers: Map<String, String>? = null,
|
|
126
|
-
attempt: Int = 0,
|
|
127
|
-
): String {
|
|
128
|
-
return try {
|
|
129
|
-
val response = client.request {
|
|
130
|
-
this.method = method
|
|
131
|
-
url {
|
|
132
|
-
// Base URL is set as default; append the path
|
|
133
|
-
val fullUrl = "${config.baseUrl.trimEnd('/')}${path}"
|
|
134
|
-
takeFrom(fullUrl)
|
|
135
|
-
|
|
136
|
-
// Add query parameters
|
|
137
|
-
query?.forEach { (key, value) ->
|
|
138
|
-
if (value != null) {
|
|
139
|
-
parameters.append(key, value)
|
|
134
|
+
if (body != null) {
|
|
135
|
+
if (headers?.keys?.none { it.equals(HttpHeaders.ContentType, ignoreCase = true) } != false) {
|
|
136
|
+
contentType(ContentType.Application.Json)
|
|
140
137
|
}
|
|
138
|
+
setBody(body)
|
|
141
139
|
}
|
|
140
|
+
|
|
141
|
+
headers?.forEach { (key, value) -> this.headers.append(key, value) }
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
setBody(body)
|
|
144
|
+
if (response.status.value in 200..299) {
|
|
145
|
+
return response.bodyAsText()
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
val error = buildSdkException(response)
|
|
149
|
+
if (!canRetry || !isRetryableStatus(response.status.value) || attempt == config.maxRetries) {
|
|
150
|
+
throw error
|
|
152
151
|
}
|
|
153
|
-
}
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
delay(backoffMs)
|
|
175
|
-
executeWithRetry(method, path, query, body, headers, attempt + 1)
|
|
176
|
-
} else {
|
|
177
|
-
throw SdkException(
|
|
178
|
-
status = null,
|
|
179
|
-
code = null,
|
|
180
|
-
message = "Network error: ${e.message}",
|
|
181
|
-
requestId = null,
|
|
182
|
-
cause = e,
|
|
183
|
-
)
|
|
153
|
+
lastError = error
|
|
154
|
+
delay(retryDelayMs(response.headers[HttpHeaders.RetryAfter], attempt))
|
|
155
|
+
} catch (error: CancellationException) {
|
|
156
|
+
throw error
|
|
157
|
+
} catch (error: SdkException) {
|
|
158
|
+
throw error
|
|
159
|
+
} catch (error: Exception) {
|
|
160
|
+
if (!canRetry || attempt == config.maxRetries) {
|
|
161
|
+
throw SdkException(
|
|
162
|
+
status = null,
|
|
163
|
+
code = null,
|
|
164
|
+
message = "Network error: ${error.message ?: error::class.simpleName}",
|
|
165
|
+
requestId = null,
|
|
166
|
+
cause = error,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
lastError = error
|
|
171
|
+
delay(retryDelayMs(null, attempt))
|
|
184
172
|
}
|
|
185
173
|
}
|
|
174
|
+
|
|
175
|
+
throw SdkException(
|
|
176
|
+
status = null,
|
|
177
|
+
code = null,
|
|
178
|
+
message = "Request failed after ${config.maxRetries + 1} attempts",
|
|
179
|
+
requestId = null,
|
|
180
|
+
cause = lastError,
|
|
181
|
+
)
|
|
186
182
|
}
|
|
187
183
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
184
|
+
override fun close() {
|
|
185
|
+
client.close()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private fun canRetry(method: HttpMethod, headers: Map<String, String>?): Boolean {
|
|
189
|
+
if (config.retryUnsafeRequests || method in RETRYABLE_METHODS) return true
|
|
190
|
+
return config.defaultHeaders.any { (name, value) ->
|
|
191
|
+
name.equals("X-Idempotency-Key", ignoreCase = true) && value.isNotEmpty()
|
|
192
|
+
} || headers?.any { (name, value) ->
|
|
193
|
+
name.equals("X-Idempotency-Key", ignoreCase = true) && value.isNotEmpty()
|
|
194
|
+
} == true
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private fun retryDelayMs(retryAfter: String?, attempt: Int): Long {
|
|
198
|
+
val serverDelay = retryAfter?.trim()?.let { value ->
|
|
199
|
+
value.toLongOrNull()?.let { seconds -> max(0L, seconds * 1_000L) }
|
|
200
|
+
?: runCatching {
|
|
201
|
+
max(
|
|
202
|
+
0L,
|
|
203
|
+
ZonedDateTime.parse(value, DateTimeFormatter.RFC_1123_DATE_TIME)
|
|
204
|
+
.toInstant()
|
|
205
|
+
.toEpochMilli() - System.currentTimeMillis(),
|
|
206
|
+
)
|
|
207
|
+
}.getOrNull()
|
|
194
208
|
}
|
|
209
|
+
if (serverDelay != null) return serverDelay.coerceAtMost(MAX_RETRY_DELAY_MS)
|
|
210
|
+
|
|
211
|
+
val base = (200L * (1L shl attempt.coerceAtMost(10))).coerceAtMost(MAX_RETRY_DELAY_MS)
|
|
212
|
+
return (base + Random.nextLong(base / 2 + 1)).coerceAtMost(MAX_RETRY_DELAY_MS)
|
|
213
|
+
}
|
|
195
214
|
|
|
196
|
-
|
|
197
|
-
|
|
215
|
+
private suspend fun buildSdkException(response: HttpResponse): SdkException {
|
|
216
|
+
val status = response.status.value
|
|
217
|
+
val body = runCatching { response.bodyAsText() }.getOrNull()
|
|
218
|
+
val requestId = response.headers["x-request-id"] ?: response.headers["request-id"]
|
|
198
219
|
|
|
199
|
-
// Try to extract structured error info from the JSON body
|
|
200
220
|
var code: String? = null
|
|
201
|
-
var message
|
|
221
|
+
var message = body?.takeIf { it.isNotBlank() } ?: "API error (status $status)"
|
|
202
222
|
|
|
203
|
-
if (body
|
|
204
|
-
|
|
223
|
+
if (!body.isNullOrBlank()) {
|
|
224
|
+
runCatching {
|
|
205
225
|
val jsonBody = json.decodeFromString<JsonObject>(body)
|
|
226
|
+
val error = jsonBody["error"]
|
|
206
227
|
code = jsonBody["code"]?.jsonPrimitive?.content
|
|
207
|
-
?:
|
|
208
|
-
when (err) {
|
|
209
|
-
is JsonObject -> err["code"]?.jsonPrimitive?.content
|
|
210
|
-
else -> err.jsonPrimitive.content
|
|
211
|
-
}
|
|
212
|
-
}
|
|
228
|
+
?: (error as? JsonObject)?.get("code")?.jsonPrimitive?.content
|
|
213
229
|
message = jsonBody["message"]?.jsonPrimitive?.content
|
|
214
|
-
?:
|
|
215
|
-
|
|
216
|
-
is JsonObject -> err["message"]?.jsonPrimitive?.content
|
|
217
|
-
else -> err.jsonPrimitive.content
|
|
218
|
-
}
|
|
219
|
-
}
|
|
230
|
+
?: (error as? JsonObject)?.get("message")?.jsonPrimitive?.content
|
|
231
|
+
?: error?.jsonPrimitive?.content
|
|
220
232
|
?: message
|
|
221
|
-
} catch (_: Exception) {
|
|
222
|
-
// Use raw body as the message
|
|
223
|
-
message = body
|
|
224
233
|
}
|
|
225
234
|
}
|
|
226
235
|
|
|
227
|
-
return SdkException(
|
|
228
|
-
status = status,
|
|
229
|
-
code = code,
|
|
230
|
-
message = message,
|
|
231
|
-
requestId = requestId,
|
|
232
|
-
)
|
|
236
|
+
return SdkException(status, code, message, requestId)
|
|
233
237
|
}
|
|
234
238
|
|
|
239
|
+
private companion object {
|
|
240
|
+
val RETRYABLE_METHODS = setOf(
|
|
241
|
+
HttpMethod.Get,
|
|
242
|
+
HttpMethod.Head,
|
|
243
|
+
HttpMethod.Options,
|
|
244
|
+
HttpMethod.Put,
|
|
245
|
+
HttpMethod.Delete,
|
|
246
|
+
)
|
|
247
|
+
val RETRYABLE_STATUSES = setOf(408, 425, 429, 500, 502, 503, 504)
|
|
248
|
+
const val MAX_RETRY_DELAY_MS = 30_000L
|
|
249
|
+
|
|
250
|
+
fun isRetryableStatus(status: Int): Boolean = status in RETRYABLE_STATUSES
|
|
251
|
+
}
|
|
235
252
|
}
|
|
@@ -1,100 +1,49 @@
|
|
|
1
1
|
package {{packageName}}.models
|
|
2
2
|
|
|
3
|
-
import kotlinx.serialization
|
|
4
|
-
import kotlinx.serialization.
|
|
5
|
-
import kotlinx.serialization.
|
|
6
|
-
import kotlinx.serialization.json.*
|
|
3
|
+
import kotlinx.serialization.SerialName
|
|
4
|
+
import kotlinx.serialization.Serializable
|
|
5
|
+
import kotlinx.serialization.json.JsonElement
|
|
7
6
|
|
|
8
7
|
{{#each schemas}}
|
|
9
|
-
{{#if
|
|
10
|
-
{{
|
|
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}}
|
|
8
|
+
{{#if external}}
|
|
9
|
+
typealias {{name}} = JsonElement
|
|
50
10
|
|
|
51
|
-
{{
|
|
52
|
-
{{#if
|
|
53
|
-
/**
|
|
54
|
-
* {{description}}
|
|
55
|
-
*/
|
|
56
|
-
{{/if}}
|
|
11
|
+
{{else if isEnum}}
|
|
12
|
+
{{#if stringEnum}}
|
|
57
13
|
@Serializable
|
|
58
14
|
enum class {{name}} {
|
|
59
|
-
|
|
60
|
-
@SerialName("{{
|
|
61
|
-
|
|
62
|
-
{{/each}}
|
|
15
|
+
{{#each enumValues}}
|
|
16
|
+
@SerialName("{{value}}") {{name}},
|
|
17
|
+
{{/each}}
|
|
63
18
|
}
|
|
19
|
+
{{else}}
|
|
20
|
+
typealias {{name}} = Double
|
|
64
21
|
{{/if}}
|
|
65
22
|
|
|
66
|
-
{{
|
|
67
|
-
{{
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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}}
|
|
23
|
+
{{else if isUnionType}}
|
|
24
|
+
typealias {{name}} = JsonElement
|
|
25
|
+
|
|
26
|
+
{{else if isIntersectionType}}
|
|
27
|
+
typealias {{name}} = JsonElement
|
|
82
28
|
|
|
83
|
-
{{
|
|
84
|
-
{{
|
|
29
|
+
{{else if isTypeAlias}}
|
|
30
|
+
typealias {{name}} = {{{aliasType}}}
|
|
31
|
+
|
|
32
|
+
{{else}}
|
|
85
33
|
{{#if description}}
|
|
86
|
-
/**
|
|
87
|
-
* {{description}}
|
|
88
|
-
*/
|
|
34
|
+
/** {{description}} */
|
|
89
35
|
{{/if}}
|
|
90
|
-
{{! Intersection type — emit as interface implemented by concrete types }}
|
|
91
36
|
@Serializable
|
|
37
|
+
{{#if hasProperties}}
|
|
92
38
|
data class {{name}}(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
39
|
+
{{#each properties}}
|
|
40
|
+
@SerialName("{{name}}")
|
|
41
|
+
val {{fieldName}}: {{{targetType}}}{{#if hasDefault}} = null{{/if}},
|
|
42
|
+
{{/each}}
|
|
96
43
|
)
|
|
97
|
-
{{
|
|
44
|
+
{{else}}
|
|
45
|
+
class {{name}}
|
|
98
46
|
{{/if}}
|
|
99
47
|
|
|
48
|
+
{{/if}}
|
|
100
49
|
{{/each}}
|
package/templates/php/client.hbs
CHANGED
|
@@ -1,74 +1,36 @@
|
|
|
1
1
|
<?php
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* @generated
|
|
5
|
-
* This file was automatically generated by Tesseract.
|
|
6
|
-
* DO NOT MODIFY THIS FILE DIRECTLY.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
3
|
declare(strict_types=1);
|
|
10
4
|
|
|
11
5
|
namespace {{namespace}};
|
|
12
6
|
|
|
13
7
|
{{#each groups}}
|
|
14
|
-
use {{
|
|
8
|
+
use {{fullClassName}};
|
|
15
9
|
{{/each}}
|
|
16
10
|
|
|
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
|
-
*/
|
|
11
|
+
/** {{title}} SDK client. */
|
|
29
12
|
class Client
|
|
30
13
|
{
|
|
31
14
|
private Transport $transport;
|
|
32
15
|
|
|
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
|
-
*/
|
|
16
|
+
/** @param array<string, mixed> $config */
|
|
44
17
|
public function __construct(array $config = [])
|
|
45
18
|
{
|
|
46
19
|
$this->transport = new Transport($config);
|
|
47
20
|
}
|
|
48
21
|
|
|
49
|
-
/**
|
|
50
|
-
* Get the underlying transport instance for advanced use.
|
|
51
|
-
*/
|
|
52
22
|
public function getTransport(): Transport
|
|
53
23
|
{
|
|
54
24
|
return $this->transport;
|
|
55
25
|
}
|
|
56
26
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
private ?{{className}} $_{{{name}}} = null;
|
|
27
|
+
{{#each groups}}
|
|
28
|
+
private ?{{className}} $_{{accessorName}} = null;
|
|
60
29
|
|
|
61
|
-
|
|
62
|
-
{{#if description}}
|
|
63
|
-
* {{description}}
|
|
64
|
-
*
|
|
65
|
-
{{/if}}
|
|
66
|
-
* @return {{className}}
|
|
67
|
-
*/
|
|
68
|
-
public function {{name}}(): {{className}}
|
|
30
|
+
public function {{accessorName}}(): {{className}}
|
|
69
31
|
{
|
|
70
|
-
return $this->_{{
|
|
32
|
+
return $this->_{{accessorName}} ??= new {{className}}($this->transport);
|
|
71
33
|
}
|
|
72
34
|
|
|
73
|
-
|
|
35
|
+
{{/each}}
|
|
74
36
|
}
|