@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,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
+ }
@@ -0,0 +1,6 @@
1
+ module {{packageName}}
2
+
3
+ go 1.21
4
+
5
+ require (
6
+ )