@apollo-deploy/tesseract 0.9.0 → 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.d.ts +3 -3
- package/dist/pipeline/intake.d.ts.map +1 -1
- package/dist/pipeline/intake.js +194 -121
- 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,291 @@
|
|
|
1
|
+
namespace {{namespace}};
|
|
2
|
+
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.IO;
|
|
6
|
+
using System.Linq;
|
|
7
|
+
using System.Net.Http;
|
|
8
|
+
using System.Text;
|
|
9
|
+
using System.Text.Json;
|
|
10
|
+
using System.Threading;
|
|
11
|
+
using System.Threading.Tasks;
|
|
12
|
+
|
|
13
|
+
/// <summary>
|
|
14
|
+
/// Configuration for the HTTP transport layer.
|
|
15
|
+
/// </summary>
|
|
16
|
+
public class TransportConfig
|
|
17
|
+
{
|
|
18
|
+
public string BaseUrl { get; set; } = "";
|
|
19
|
+
{{#each securitySchemes}}
|
|
20
|
+
public string? {{pascalCase configKey}} { get; set; }
|
|
21
|
+
{{/each}}
|
|
22
|
+
public int TimeoutMs { get; set; } = 15000;
|
|
23
|
+
public int MaxRetries { get; set; } = 3;
|
|
24
|
+
public Dictionary<string, string>? DefaultHeaders { get; set; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// <summary>
|
|
28
|
+
/// Low-level HTTP transport handling authentication, serialization, retries, and error normalization.
|
|
29
|
+
/// </summary>
|
|
30
|
+
public class Transport
|
|
31
|
+
{
|
|
32
|
+
private readonly TransportConfig _config;
|
|
33
|
+
private readonly HttpClient _httpClient;
|
|
34
|
+
private static readonly JsonSerializerOptions _jsonOptions = new()
|
|
35
|
+
{
|
|
36
|
+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
37
|
+
PropertyNameCaseInsensitive = true,
|
|
38
|
+
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
public Transport(TransportConfig config)
|
|
42
|
+
{
|
|
43
|
+
_config = config;
|
|
44
|
+
_httpClient = new HttpClient
|
|
45
|
+
{
|
|
46
|
+
Timeout = TimeSpan.FromMilliseconds(config.TimeoutMs),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public class Request
|
|
51
|
+
{
|
|
52
|
+
public HttpMethod Method { get; set; } = HttpMethod.Get;
|
|
53
|
+
public string Path { get; set; } = "";
|
|
54
|
+
public Dictionary<string, string>? Query { get; set; }
|
|
55
|
+
public object? Body { get; set; }
|
|
56
|
+
public string? ContentType { get; set; }
|
|
57
|
+
public Dictionary<string, string>? Headers { get; set; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public async Task<T> ExecuteAsync<T>(Request request, CancellationToken cancellationToken = default)
|
|
61
|
+
{
|
|
62
|
+
return await ExecuteWithRetriesAsync<T>(request, _config.MaxRetries, cancellationToken);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public async Task<byte[]> ExecuteRawAsync(Request request, CancellationToken cancellationToken = default)
|
|
66
|
+
{
|
|
67
|
+
return await ExecuteRawWithRetriesAsync(request, _config.MaxRetries, cancellationToken);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public async Task<HttpResponseMessage> StreamAsync(Request request, CancellationToken cancellationToken = default)
|
|
71
|
+
{
|
|
72
|
+
using var httpRequest = BuildRequest(request);
|
|
73
|
+
var response = await _httpClient.SendAsync(
|
|
74
|
+
httpRequest,
|
|
75
|
+
HttpCompletionOption.ResponseHeadersRead,
|
|
76
|
+
cancellationToken);
|
|
77
|
+
if (!response.IsSuccessStatusCode)
|
|
78
|
+
{
|
|
79
|
+
var errorBody = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
80
|
+
throw ParseErrorResponse(response, errorBody);
|
|
81
|
+
}
|
|
82
|
+
return response;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async Task<T> ExecuteWithRetriesAsync<T>(
|
|
86
|
+
Request request, int maxRetries, CancellationToken cancellationToken)
|
|
87
|
+
{
|
|
88
|
+
Exception? lastException = null;
|
|
89
|
+
for (int attempt = 0; attempt <= maxRetries; attempt++)
|
|
90
|
+
{
|
|
91
|
+
if (attempt > 0)
|
|
92
|
+
{
|
|
93
|
+
var backoff = (int)Math.Pow(2, attempt - 1) * 200;
|
|
94
|
+
var jitter = Random.Shared.Next(backoff / 2);
|
|
95
|
+
await Task.Delay(backoff + jitter, cancellationToken);
|
|
96
|
+
}
|
|
97
|
+
try
|
|
98
|
+
{
|
|
99
|
+
using var httpRequest = BuildRequest(request);
|
|
100
|
+
using var response = await _httpClient.SendAsync(httpRequest, cancellationToken);
|
|
101
|
+
var body = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
102
|
+
if (response.IsSuccessStatusCode)
|
|
103
|
+
{
|
|
104
|
+
if (string.IsNullOrEmpty(body)) return default!;
|
|
105
|
+
var result = JsonSerializer.Deserialize<T>(body, _jsonOptions);
|
|
106
|
+
if (result == null)
|
|
107
|
+
throw new SdkException((int)response.StatusCode, "DESERIALIZE_ERROR",
|
|
108
|
+
"Failed to deserialize response body", GetRequestId(response));
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
if ((int)response.StatusCode >= 500 && attempt < maxRetries)
|
|
112
|
+
{
|
|
113
|
+
lastException = ParseErrorResponse(response, body);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
throw ParseErrorResponse(response, body);
|
|
117
|
+
}
|
|
118
|
+
catch (SdkException) { throw; }
|
|
119
|
+
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested) { throw; }
|
|
120
|
+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; }
|
|
121
|
+
catch (Exception ex) when (IsRetryable(ex) && attempt < maxRetries) { lastException = ex; }
|
|
122
|
+
catch (Exception ex)
|
|
123
|
+
{
|
|
124
|
+
throw new SdkException(0, "REQUEST_ERROR", ex.Message, null, ex);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw new SdkException(0, "MAX_RETRIES_EXCEEDED",
|
|
128
|
+
"Request failed after " + (maxRetries + 1) + " attempts", null, lastException);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async Task<byte[]> ExecuteRawWithRetriesAsync(
|
|
132
|
+
Request request, int maxRetries, CancellationToken cancellationToken)
|
|
133
|
+
{
|
|
134
|
+
Exception? lastException = null;
|
|
135
|
+
for (int attempt = 0; attempt <= maxRetries; attempt++)
|
|
136
|
+
{
|
|
137
|
+
if (attempt > 0)
|
|
138
|
+
{
|
|
139
|
+
var backoff = (int)Math.Pow(2, attempt - 1) * 200;
|
|
140
|
+
var jitter = Random.Shared.Next(backoff / 2);
|
|
141
|
+
await Task.Delay(backoff + jitter, cancellationToken);
|
|
142
|
+
}
|
|
143
|
+
try
|
|
144
|
+
{
|
|
145
|
+
using var httpRequest = BuildRequest(request);
|
|
146
|
+
using var response = await _httpClient.SendAsync(httpRequest, cancellationToken);
|
|
147
|
+
var body = await response.Content.ReadAsByteArrayAsync(cancellationToken);
|
|
148
|
+
if (response.IsSuccessStatusCode) return body;
|
|
149
|
+
var bodyString = Encoding.UTF8.GetString(body);
|
|
150
|
+
if ((int)response.StatusCode >= 500 && attempt < maxRetries)
|
|
151
|
+
{
|
|
152
|
+
lastException = ParseErrorResponse(response, bodyString);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
throw ParseErrorResponse(response, bodyString);
|
|
156
|
+
}
|
|
157
|
+
catch (SdkException) { throw; }
|
|
158
|
+
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested) { throw; }
|
|
159
|
+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; }
|
|
160
|
+
catch (Exception ex) when (IsRetryable(ex) && attempt < maxRetries) { lastException = ex; }
|
|
161
|
+
catch (Exception ex)
|
|
162
|
+
{
|
|
163
|
+
throw new SdkException(0, "REQUEST_ERROR", ex.Message, null, ex);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
throw new SdkException(0, "MAX_RETRIES_EXCEEDED",
|
|
167
|
+
"Request failed after " + (maxRetries + 1) + " attempts", null, lastException);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private HttpRequestMessage BuildRequest(Request request)
|
|
171
|
+
{
|
|
172
|
+
var baseUri = new Uri(_config.BaseUrl.TrimEnd('/') + "/");
|
|
173
|
+
var fullUri = new Uri(baseUri, request.Path.TrimStart('/'));
|
|
174
|
+
|
|
175
|
+
if (request.Query?.Count > 0)
|
|
176
|
+
{
|
|
177
|
+
var queryString = string.Join("&",
|
|
178
|
+
request.Query.Select(kv =>
|
|
179
|
+
Uri.EscapeDataString(kv.Key) + "=" + Uri.EscapeDataString(kv.Value)));
|
|
180
|
+
var separator = fullUri.Query.Length > 0 ? "&" : "";
|
|
181
|
+
fullUri = new Uri(fullUri, fullUri.PathAndQuery + separator + queryString);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
var httpRequest = new HttpRequestMessage(request.Method, fullUri);
|
|
185
|
+
|
|
186
|
+
if (_config.DefaultHeaders != null)
|
|
187
|
+
{
|
|
188
|
+
foreach (var (key, value) in _config.DefaultHeaders)
|
|
189
|
+
{
|
|
190
|
+
httpRequest.Headers.TryAddWithoutValidation(key, value);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
{{#each securitySchemes}}
|
|
195
|
+
{{#if (eq type "apiKey")}}
|
|
196
|
+
{{#if (eq in "header")}}
|
|
197
|
+
if (!string.IsNullOrEmpty(_config.{{pascalCase configKey}}))
|
|
198
|
+
httpRequest.Headers.TryAddWithoutValidation("{{paramName}}", _config.{{pascalCase configKey}});
|
|
199
|
+
{{/if}}
|
|
200
|
+
{{#if (eq in "query")}}
|
|
201
|
+
if (!string.IsNullOrEmpty(_config.{{pascalCase configKey}}))
|
|
202
|
+
{
|
|
203
|
+
var sep = fullUri.Query.Length > 0 ? "&" : "";
|
|
204
|
+
fullUri = new Uri(fullUri, fullUri.PathAndQuery + sep + "{{paramName}}=" + Uri.EscapeDataString(_config.{{pascalCase configKey}}));
|
|
205
|
+
httpRequest.RequestUri = fullUri;
|
|
206
|
+
}
|
|
207
|
+
{{/if}}
|
|
208
|
+
{{/if}}
|
|
209
|
+
{{#if (and (eq type "http") (eq scheme "bearer"))}}
|
|
210
|
+
if (!string.IsNullOrEmpty(_config.{{pascalCase configKey}}))
|
|
211
|
+
httpRequest.Headers.TryAddWithoutValidation("Authorization", "Bearer " + _config.{{pascalCase configKey}});
|
|
212
|
+
{{/if}}
|
|
213
|
+
{{#if (eq type "oauth2")}}
|
|
214
|
+
if (!string.IsNullOrEmpty(_config.{{pascalCase configKey}}))
|
|
215
|
+
httpRequest.Headers.TryAddWithoutValidation("Authorization", "Bearer " + _config.{{pascalCase configKey}});
|
|
216
|
+
{{/if}}
|
|
217
|
+
{{#if (eq type "openIdConnect")}}
|
|
218
|
+
if (!string.IsNullOrEmpty(_config.{{pascalCase configKey}}))
|
|
219
|
+
httpRequest.Headers.TryAddWithoutValidation("Authorization", "Bearer " + _config.{{pascalCase configKey}});
|
|
220
|
+
{{/if}}
|
|
221
|
+
{{/each}}
|
|
222
|
+
|
|
223
|
+
if (request.Body != null)
|
|
224
|
+
{
|
|
225
|
+
var contentType = request.ContentType ?? "application/json";
|
|
226
|
+
var json = JsonSerializer.Serialize(request.Body, _jsonOptions);
|
|
227
|
+
httpRequest.Content = new StringContent(json, Encoding.UTF8, contentType);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (request.Headers != null)
|
|
231
|
+
{
|
|
232
|
+
foreach (var (key, value) in request.Headers)
|
|
233
|
+
{
|
|
234
|
+
if (key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase) && httpRequest.Content != null)
|
|
235
|
+
{
|
|
236
|
+
httpRequest.Content.Headers.ContentType =
|
|
237
|
+
new System.Net.Http.Headers.MediaTypeHeaderValue(value);
|
|
238
|
+
}
|
|
239
|
+
else
|
|
240
|
+
{
|
|
241
|
+
httpRequest.Headers.TryAddWithoutValidation(key, value);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return httpRequest;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private static bool IsRetryable(Exception ex)
|
|
250
|
+
{
|
|
251
|
+
return ex is HttpRequestException ||
|
|
252
|
+
ex is TaskCanceledException ||
|
|
253
|
+
ex is IOException ||
|
|
254
|
+
ex.Message.Contains("connection") ||
|
|
255
|
+
ex.Message.Contains("timeout") ||
|
|
256
|
+
ex.Message.Contains("reset");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private static SdkException ParseErrorResponse(HttpResponseMessage response, string body)
|
|
260
|
+
{
|
|
261
|
+
var requestId = GetRequestId(response);
|
|
262
|
+
try
|
|
263
|
+
{
|
|
264
|
+
using var doc = JsonDocument.Parse(body);
|
|
265
|
+
var root = doc.RootElement;
|
|
266
|
+
var code = "";
|
|
267
|
+
var message = "";
|
|
268
|
+
if (root.TryGetProperty("code", out var codeEl)) code = codeEl.GetString() ?? "";
|
|
269
|
+
if (root.TryGetProperty("message", out var msgEl)) message = msgEl.GetString() ?? "";
|
|
270
|
+
else if (root.TryGetProperty("error", out var errEl)) message = errEl.GetString() ?? "";
|
|
271
|
+
if (string.IsNullOrEmpty(message)) message = body.Trim();
|
|
272
|
+
return new SdkException((int)response.StatusCode, code, message, requestId);
|
|
273
|
+
}
|
|
274
|
+
catch (JsonException)
|
|
275
|
+
{
|
|
276
|
+
return new SdkException((int)response.StatusCode, "",
|
|
277
|
+
string.IsNullOrWhiteSpace(body)
|
|
278
|
+
? "HTTP " + (int)response.StatusCode + ": " + response.ReasonPhrase
|
|
279
|
+
: body.Trim(), requestId);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private static string? GetRequestId(HttpResponseMessage response)
|
|
284
|
+
{
|
|
285
|
+
if (response.Headers.TryGetValues("X-Request-ID", out var values))
|
|
286
|
+
return values.FirstOrDefault();
|
|
287
|
+
if (response.Headers.TryGetValues("x-request-id", out values))
|
|
288
|
+
return values.FirstOrDefault();
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
namespace {{namespace}};
|
|
2
|
+
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Text.Json;
|
|
6
|
+
using System.Text.Json.Serialization;
|
|
7
|
+
|
|
8
|
+
{{#each schemas}}
|
|
9
|
+
{{#if (neq ownership.kind "external")}}
|
|
10
|
+
{{#if isEnum}}
|
|
11
|
+
/// <summary>
|
|
12
|
+
{{#if description}}
|
|
13
|
+
/// {{description}}
|
|
14
|
+
{{else}}
|
|
15
|
+
/// {{name}} enumeration.
|
|
16
|
+
{{/if}}
|
|
17
|
+
/// </summary>
|
|
18
|
+
[JsonConverter(typeof(JsonStringEnumConverter))]
|
|
19
|
+
public enum {{name}}
|
|
20
|
+
{
|
|
21
|
+
{{#each enumValues}}
|
|
22
|
+
/// <summary>
|
|
23
|
+
/// {{this}} value.
|
|
24
|
+
/// </summary>
|
|
25
|
+
[JsonStringEnumMemberName("{{this}}")]
|
|
26
|
+
{{pascalCase ../name}}{{pascalCase this}},
|
|
27
|
+
{{/each}}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
{{else if isUnionType}}
|
|
31
|
+
/// <summary>
|
|
32
|
+
{{#if description}}
|
|
33
|
+
/// {{description}}
|
|
34
|
+
{{else}}
|
|
35
|
+
/// {{name}} union type.
|
|
36
|
+
{{/if}}
|
|
37
|
+
/// </summary>
|
|
38
|
+
public class {{name}}
|
|
39
|
+
{
|
|
40
|
+
private readonly object _value;
|
|
41
|
+
private readonly Type _type;
|
|
42
|
+
|
|
43
|
+
{{#each unionMembers}}
|
|
44
|
+
/// <summary>
|
|
45
|
+
/// Creates a {{../name}} from a {{this}} value.
|
|
46
|
+
/// </summary>
|
|
47
|
+
public {{../name}}({{this}} value)
|
|
48
|
+
{
|
|
49
|
+
_value = value!;
|
|
50
|
+
_type = typeof({{this}});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
{{/each}}
|
|
54
|
+
|
|
55
|
+
/// <summary>
|
|
56
|
+
/// Gets the underlying value.
|
|
57
|
+
/// </summary>
|
|
58
|
+
public object Value => _value;
|
|
59
|
+
|
|
60
|
+
/// <summary>
|
|
61
|
+
/// Attempts to get the value as the specified type.
|
|
62
|
+
/// </summary>
|
|
63
|
+
public bool TryGet<T>(out T? value)
|
|
64
|
+
{
|
|
65
|
+
if (_value is T v)
|
|
66
|
+
{
|
|
67
|
+
value = v;
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
value = default;
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
{{else}}
|
|
76
|
+
/// <summary>
|
|
77
|
+
{{#if description}}
|
|
78
|
+
/// {{description}}
|
|
79
|
+
{{else}}
|
|
80
|
+
/// {{name}} schema type.
|
|
81
|
+
{{/if}}
|
|
82
|
+
/// </summary>
|
|
83
|
+
public class {{name}}
|
|
84
|
+
{
|
|
85
|
+
{{#each properties}}
|
|
86
|
+
/// <summary>
|
|
87
|
+
{{#if description}}
|
|
88
|
+
/// {{description}}
|
|
89
|
+
{{/if}}
|
|
90
|
+
/// </summary>
|
|
91
|
+
[JsonPropertyName("{{name}}")]
|
|
92
|
+
{{#if (or nullable (not required))}}
|
|
93
|
+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
94
|
+
{{/if}}
|
|
95
|
+
{{#if deprecated}}
|
|
96
|
+
[Obsolete]
|
|
97
|
+
{{/if}}
|
|
98
|
+
public {{#if (eq type "string")}}{{#if (eq format "date-time")}}DateTime{{else}}string{{/if}}{{else if (eq type "integer")}}int{{else if (eq type "number")}}double{{else if (eq type "boolean")}}bool{{else if (eq type "array")}}List<object>?{{else}}{{type}}{{/if}}{{#if (or nullable (not required))}}?{{/if}} {{pascalCase name}} { get; set; }
|
|
99
|
+
{{/each}}
|
|
100
|
+
}
|
|
101
|
+
{{/if}}
|
|
102
|
+
|
|
103
|
+
{{/if}}
|
|
104
|
+
{{/each}}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
package {{pkgName}}
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"time"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// ── Client Configuration ──────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
// ClientConfig holds all configuration for the API client.
|
|
11
|
+
type ClientConfig struct {
|
|
12
|
+
BaseURL string
|
|
13
|
+
Timeout time.Duration
|
|
14
|
+
MaxRetries int
|
|
15
|
+
DefaultHeaders map[string]string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ClientOption is a functional option for configuring the client.
|
|
19
|
+
type ClientOption func(*ClientConfig)
|
|
20
|
+
|
|
21
|
+
// WithBaseURL sets the base URL for all API requests.
|
|
22
|
+
func WithBaseURL(url string) ClientOption {
|
|
23
|
+
return func(c *ClientConfig) { c.BaseURL = url }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// WithTimeout sets the request timeout.
|
|
27
|
+
func WithTimeout(d time.Duration) ClientOption {
|
|
28
|
+
return func(c *ClientConfig) { c.Timeout = d }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// WithMaxRetries sets the maximum number of retry attempts.
|
|
32
|
+
func WithMaxRetries(n int) ClientOption {
|
|
33
|
+
return func(c *ClientConfig) { c.MaxRetries = n }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// WithDefaultHeader adds a default header sent with every request.
|
|
37
|
+
func WithDefaultHeader(key, value string) ClientOption {
|
|
38
|
+
return func(c *ClientConfig) {
|
|
39
|
+
if c.DefaultHeaders == nil {
|
|
40
|
+
c.DefaultHeaders = make(map[string]string)
|
|
41
|
+
}
|
|
42
|
+
c.DefaultHeaders[key] = value
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// DefaultConfig returns a ClientConfig populated with sensible defaults.
|
|
47
|
+
func DefaultConfig() ClientConfig {
|
|
48
|
+
return ClientConfig{
|
|
49
|
+
BaseURL: "{{baseUrl}}",
|
|
50
|
+
Timeout: 15 * time.Second,
|
|
51
|
+
MaxRetries: 3,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Client ────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
// Client is the primary API client for {{title}}.
|
|
58
|
+
// It is safe for concurrent use by multiple goroutines.
|
|
59
|
+
type Client struct {
|
|
60
|
+
config ClientConfig
|
|
61
|
+
transport *Transport
|
|
62
|
+
{{#each groups}}
|
|
63
|
+
{{pascalCase name}} *{{interfaceName}}
|
|
64
|
+
{{/each}}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// NewClient creates a new {{title}} API client with the given options.
|
|
68
|
+
//
|
|
69
|
+
// Example:
|
|
70
|
+
//
|
|
71
|
+
// client := petshop.NewClient(
|
|
72
|
+
// petshop.WithBaseURL("https://api.example.com"),
|
|
73
|
+
// petshop.WithTimeout(30*time.Second),
|
|
74
|
+
// )
|
|
75
|
+
// pets, err := client.Pets.List(ctx, nil)
|
|
76
|
+
func NewClient(opts ...ClientOption) *Client {
|
|
77
|
+
cfg := DefaultConfig()
|
|
78
|
+
for _, opt := range opts {
|
|
79
|
+
opt(&cfg)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
t := NewTransport(TransportConfig{
|
|
83
|
+
BaseURL: cfg.BaseURL,
|
|
84
|
+
Timeout: cfg.Timeout,
|
|
85
|
+
MaxRetries: cfg.MaxRetries,
|
|
86
|
+
Headers: cfg.DefaultHeaders,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
return &Client{
|
|
90
|
+
config: cfg,
|
|
91
|
+
transport: t,
|
|
92
|
+
{{#each groups}}
|
|
93
|
+
{{pascalCase name}}: &{{interfaceName}}{transport: t},
|
|
94
|
+
{{/each}}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Transport returns the underlying HTTP transport for advanced use cases.
|
|
99
|
+
func (c *Client) Transport() *Transport {
|
|
100
|
+
return c.transport
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Request / Response types ──────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
// RequestOptions holds per-request overrides.
|
|
106
|
+
type RequestOptions struct {
|
|
107
|
+
// Timeout overrides the client-level timeout for this request.
|
|
108
|
+
Timeout time.Duration
|
|
109
|
+
// Headers are additional headers merged into the request.
|
|
110
|
+
Headers map[string]string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ListParams is embedded by list operation parameter types to provide
|
|
114
|
+
// common pagination and filtering fields.
|
|
115
|
+
type ListParams struct {
|
|
116
|
+
Page *int `json:"page,omitempty"`
|
|
117
|
+
Limit *int `json:"limit,omitempty"`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Int returns a pointer to the given int value. Useful for optional
|
|
121
|
+
// parameters in request structs.
|
|
122
|
+
func Int(v int) *int { return &v }
|
|
123
|
+
|
|
124
|
+
// String returns a pointer to the given string value.
|
|
125
|
+
func String(v string) *string { return &v }
|
|
126
|
+
|
|
127
|
+
// Bool returns a pointer to the given bool value.
|
|
128
|
+
func Bool(v bool) *bool { return &v }
|
|
129
|
+
|
|
130
|
+
{{#if hasSSE}}
|
|
131
|
+
// ── SSE Types ─────────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
// SSEEvent represents a parsed Server-Sent Event.
|
|
134
|
+
type SSEEvent struct {
|
|
135
|
+
Event string
|
|
136
|
+
Data string
|
|
137
|
+
ID string
|
|
138
|
+
Retry int
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// SSEOption configures an SSE stream subscription.
|
|
142
|
+
type SSEOption func(*sseConfig)
|
|
143
|
+
|
|
144
|
+
type sseConfig struct {
|
|
145
|
+
signal context.Context
|
|
146
|
+
eventTypes []string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// WithSSESignal sets a context for cancellation of the SSE stream.
|
|
150
|
+
func WithSSESignal(ctx context.Context) SSEOption {
|
|
151
|
+
return func(c *sseConfig) { c.signal = ctx }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// WithSSEEventTypes filters the stream to specific event types.
|
|
155
|
+
func WithSSEEventTypes(types ...string) SSEOption {
|
|
156
|
+
return func(c *sseConfig) { c.eventTypes = types }
|
|
157
|
+
}
|
|
158
|
+
{{/if}}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
package {{pkgName}}
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
{{#if hasSSE}}
|
|
5
|
+
"bufio"
|
|
6
|
+
"strings"
|
|
7
|
+
{{/if}}
|
|
8
|
+
"context"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// {{interfaceName}} provides access to {{name}} API operations.
|
|
12
|
+
type {{interfaceName}} struct {
|
|
13
|
+
transport *Transport
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
{{#each operations}}
|
|
17
|
+
{{#if isEventStream}}
|
|
18
|
+
func (api *{{../interfaceName}}) {{pascalCase name}}Stream(
|
|
19
|
+
ctx context.Context,
|
|
20
|
+
{{#each pathParams}}
|
|
21
|
+
{{name}} string,
|
|
22
|
+
{{/each}}
|
|
23
|
+
{{#if queryType}}
|
|
24
|
+
params {{goQueryType queryType}},
|
|
25
|
+
{{/if}}
|
|
26
|
+
) (<-chan SSEEvent, <-chan error) {
|
|
27
|
+
eventCh := make(chan SSEEvent)
|
|
28
|
+
errCh := make(chan error, 1)
|
|
29
|
+
go func() {
|
|
30
|
+
defer close(eventCh)
|
|
31
|
+
defer close(errCh)
|
|
32
|
+
reqPath := buildPath("{{path}}"{{#each pathParams}}, "{{originalName}}", {{name}}{{/each}})
|
|
33
|
+
resp, err := api.transport.Stream(ctx, request{Method: "{{httpMethod}}", Path: reqPath})
|
|
34
|
+
if err != nil { errCh <- err; return }
|
|
35
|
+
defer resp.Body.Close()
|
|
36
|
+
scanner := bufio.NewScanner(resp.Body)
|
|
37
|
+
var current SSEEvent
|
|
38
|
+
for scanner.Scan() {
|
|
39
|
+
select {
|
|
40
|
+
case <-ctx.Done(): errCh <- ctx.Err(); return
|
|
41
|
+
default:
|
|
42
|
+
}
|
|
43
|
+
line := scanner.Text()
|
|
44
|
+
if line == "" {
|
|
45
|
+
if current.Data != "" { eventCh <- current; current = SSEEvent{} }
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
if strings.HasPrefix(line, "event:") { current.Event = strings.TrimSpace(line[6:]) }
|
|
49
|
+
if strings.HasPrefix(line, "data:") { if current.Data != "" { current.Data += "\n" }; current.Data += strings.TrimSpace(line[5:]) }
|
|
50
|
+
if strings.HasPrefix(line, "id:") { current.ID = strings.TrimSpace(line[3:]) }
|
|
51
|
+
}
|
|
52
|
+
}()
|
|
53
|
+
return eventCh, errCh
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
{{else}}
|
|
57
|
+
func (api *{{../interfaceName}}) {{pascalCase name}}(
|
|
58
|
+
ctx context.Context,
|
|
59
|
+
{{#each pathParams}}
|
|
60
|
+
{{name}} string,
|
|
61
|
+
{{/each}}
|
|
62
|
+
{{#if requestBody}}
|
|
63
|
+
body *{{requestBody.type}},
|
|
64
|
+
{{/if}}
|
|
65
|
+
{{#if queryType}}
|
|
66
|
+
params {{goQueryType queryType}},
|
|
67
|
+
{{/if}}
|
|
68
|
+
) {{#if (eq responseType "void")}}error{{else}}(*{{responseType}}, error){{/if}} {
|
|
69
|
+
reqPath := buildPath("{{path}}"{{#each pathParams}}, "{{originalName}}", {{name}}{{/each}})
|
|
70
|
+
{{#if queryType}}q := buildQuery(params){{/if}}
|
|
71
|
+
{{#if (eq responseType "void")}}
|
|
72
|
+
return api.transport.Execute(ctx, request{
|
|
73
|
+
Method: "{{httpMethod}}", Path: reqPath,
|
|
74
|
+
{{#if queryType}} Query: q,{{/if}}
|
|
75
|
+
{{#if requestBody}} Body: body,{{/if}}
|
|
76
|
+
}, nil)
|
|
77
|
+
{{else}}
|
|
78
|
+
var result {{responseType}}
|
|
79
|
+
err := api.transport.Execute(ctx, request{
|
|
80
|
+
Method: "{{httpMethod}}", Path: reqPath,
|
|
81
|
+
{{#if queryType}} Query: q,{{/if}}
|
|
82
|
+
{{#if requestBody}} Body: body,{{/if}}
|
|
83
|
+
}, &result)
|
|
84
|
+
if err != nil { return nil, err }
|
|
85
|
+
return &result, nil
|
|
86
|
+
{{/if}}
|
|
87
|
+
}
|
|
88
|
+
{{/if}}
|
|
89
|
+
{{/each}}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package {{pkgName}}
|
|
2
|
+
|
|
3
|
+
import "fmt"
|
|
4
|
+
|
|
5
|
+
// SDKError represents an error returned by the API.
|
|
6
|
+
type SDKError struct {
|
|
7
|
+
// Status is the HTTP status code.
|
|
8
|
+
Status int
|
|
9
|
+
// Code is a machine-readable error code from the API.
|
|
10
|
+
Code string
|
|
11
|
+
// RequestID is the unique identifier for the request (for support).
|
|
12
|
+
RequestID string
|
|
13
|
+
// Message is a human-readable error description.
|
|
14
|
+
Message string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Error implements the error interface.
|
|
18
|
+
func (e *SDKError) Error() string {
|
|
19
|
+
if e.Code != "" {
|
|
20
|
+
return fmt.Sprintf("[%d] %s: %s", e.Status, e.Code, e.Message)
|
|
21
|
+
}
|
|
22
|
+
return fmt.Sprintf("[%d] %s", e.Status, e.Message)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Unwrap returns nil since SDKError is the root cause.
|
|
26
|
+
func (e *SDKError) Unwrap() error {
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// NewSDKError creates a new SDKError.
|
|
31
|
+
func NewSDKError(status int, code, message, requestID string) *SDKError {
|
|
32
|
+
return &SDKError{
|
|
33
|
+
Status: status,
|
|
34
|
+
Code: code,
|
|
35
|
+
Message: message,
|
|
36
|
+
RequestID: requestID,
|
|
37
|
+
}
|
|
38
|
+
}
|