@apollo-deploy/tesseract 0.9.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) 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 +186 -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 +2 -2
  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/cli.js +2 -1
  40. package/dist/cli.js.map +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +14 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/pipeline/intake.js +23 -16
  45. package/dist/pipeline/intake.js.map +1 -1
  46. package/dist/types/ir.d.ts +1 -1
  47. package/dist/types/ir.d.ts.map +1 -1
  48. package/dist/types/sdk-module.d.ts +22 -2
  49. package/dist/types/sdk-module.d.ts.map +1 -1
  50. package/dist/types/sdk-module.js +1 -1
  51. package/package.json +1 -1
  52. package/templates/csharp/client.hbs +95 -0
  53. package/templates/csharp/csproj.hbs +18 -0
  54. package/templates/csharp/domain.hbs +183 -0
  55. package/templates/csharp/errors.hbs +45 -0
  56. package/templates/csharp/readme.hbs +76 -0
  57. package/templates/csharp/transport.hbs +291 -0
  58. package/templates/csharp/types.hbs +104 -0
  59. package/templates/go/client.hbs +158 -0
  60. package/templates/go/domain.hbs +89 -0
  61. package/templates/go/errors.hbs +38 -0
  62. package/templates/go/go-mod.hbs +6 -0
  63. package/templates/go/readme.hbs +73 -0
  64. package/templates/go/transport.hbs +379 -0
  65. package/templates/go/types.hbs +80 -0
  66. package/templates/go/utils.hbs +45 -0
  67. package/templates/kotlin/build-gradle.hbs +41 -0
  68. package/templates/kotlin/client.hbs +57 -0
  69. package/templates/kotlin/domain.hbs +73 -0
  70. package/templates/kotlin/errors.hbs +42 -0
  71. package/templates/kotlin/gradle-properties.hbs +2 -0
  72. package/templates/kotlin/readme.hbs +105 -0
  73. package/templates/kotlin/settings-gradle.hbs +1 -0
  74. package/templates/kotlin/transport.hbs +246 -0
  75. package/templates/kotlin/types.hbs +100 -0
  76. package/templates/php/client.hbs +74 -0
  77. package/templates/php/composer.hbs +19 -0
  78. package/templates/php/domain.hbs +173 -0
  79. package/templates/php/errors.hbs +131 -0
  80. package/templates/php/readme.hbs +89 -0
  81. package/templates/php/transport.hbs +285 -0
  82. package/templates/php/types.hbs +95 -0
  83. package/templates/python/client.hbs +102 -0
  84. package/templates/python/domain.hbs +139 -0
  85. package/templates/python/index.hbs +20 -0
  86. package/templates/python/pyproject.hbs +26 -0
  87. package/templates/python/readme.hbs +64 -0
  88. package/templates/python/resources-init.hbs +11 -0
  89. package/templates/python/transport.hbs +191 -0
  90. package/templates/python/types-errors.hbs +75 -0
  91. package/templates/python/types.hbs +79 -0
  92. package/templates/ruby/Gemfile.hbs +5 -0
  93. package/templates/ruby/client.hbs +86 -0
  94. package/templates/ruby/domain.hbs +110 -0
  95. package/templates/ruby/errors.hbs +150 -0
  96. package/templates/ruby/gemspec.hbs +30 -0
  97. package/templates/ruby/index.hbs +54 -0
  98. package/templates/ruby/readme.hbs +113 -0
  99. package/templates/ruby/transport.hbs +177 -0
  100. package/templates/ruby/types.hbs +126 -0
  101. package/templates/ruby/version.hbs +5 -0
  102. package/templates/rust/api-mod.hbs +3 -0
  103. package/templates/rust/cargo.hbs +22 -0
  104. package/templates/rust/client.hbs +114 -0
  105. package/templates/rust/domain.hbs +94 -0
  106. package/templates/rust/error.hbs +74 -0
  107. package/templates/rust/lib.hbs +85 -0
  108. package/templates/rust/readme.hbs +77 -0
  109. package/templates/rust/transport.hbs +287 -0
  110. package/templates/rust/types.hbs +142 -0
@@ -0,0 +1,183 @@
1
+ namespace {{namespace}}.Api;
2
+
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.IO;
6
+ using System.Net.Http;
7
+ using System.Text;
8
+ using System.Text.Json;
9
+ using System.Threading;
10
+ using System.Threading.Tasks;
11
+
12
+ /// <summary>
13
+ /// Provides access to {{name}} API operations.
14
+ /// </summary>
15
+ public class {{interfaceName}}
16
+ {
17
+ private readonly Transport _transport;
18
+
19
+ /// <summary>
20
+ /// Creates a new {{interfaceName}} instance.
21
+ /// </summary>
22
+ public {{interfaceName}}(Transport transport)
23
+ {
24
+ _transport = transport;
25
+ }
26
+
27
+ {{#each operations}}
28
+ {{#if isEventStream}}
29
+ /// <summary>
30
+ {{#if summary}}
31
+ /// {{summary}}
32
+ {{/if}}
33
+ {{#if description}}
34
+ /// {{description}}
35
+ {{/if}}
36
+ {{#if deprecated}}
37
+ /// [Obsolete] {{#if deprecationMessage}}{{deprecationMessage}}{{/if}}
38
+ {{/if}}
39
+ /// </summary>
40
+ {{#if deprecated}}
41
+ [Obsolete({{#if deprecationMessage}}"{{deprecationMessage}}"{{/if}})]
42
+ {{/if}}
43
+ public async IAsyncEnumerable<string> {{pascalCase name}}Async(
44
+ {{#each pathParams}}
45
+ {{type}}{{#if (not required)}}?{{/if}} {{name}},
46
+ {{/each}}
47
+ {{#if requestBody}}
48
+ {{requestBody.type}}{{#if (not requestBody.required)}}?{{/if}} body,
49
+ {{/if}}
50
+ {{#if queryType}}
51
+ {{queryType}}? query = null,
52
+ {{/if}}
53
+ CancellationToken cancellationToken = default)
54
+ {
55
+ var path = $"{{path}}"
56
+ {{#each pathParams}}
57
+ .Replace("{{{originalName}}}", Uri.EscapeDataString({{name}}?.ToString() ?? ""))
58
+ {{/each}}
59
+ ;
60
+
61
+ var queryParams = new Dictionary<string, string>();
62
+ {{#if queryType}}
63
+ if (query != null)
64
+ {
65
+ var json = JsonSerializer.Serialize(query);
66
+ var dict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
67
+ if (dict != null)
68
+ {
69
+ foreach (var (key, value) in dict)
70
+ {
71
+ if (value.ValueKind != JsonValueKind.Null)
72
+ queryParams[key] = value.ToString();
73
+ }
74
+ }
75
+ }
76
+ {{/if}}
77
+ {{#if (hasQueryParams this)}}
78
+ {{#each queryParams}}
79
+ if ({{name}} != null)
80
+ queryParams["{{originalName}}"] = {{name}}.ToString();
81
+ {{/each}}
82
+ {{/if}}
83
+
84
+ var response = await _transport.StreamAsync(
85
+ new Transport.Request
86
+ {
87
+ Method = HttpMethod.{{httpMethod}},
88
+ Path = path,
89
+ Query = queryParams.Count > 0 ? queryParams : null,
90
+ {{#if requestBody}}
91
+ Body = body,
92
+ ContentType = "{{requestBody.contentType}}",
93
+ {{/if}}
94
+ },
95
+ cancellationToken);
96
+
97
+ using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
98
+ using var reader = new StreamReader(stream);
99
+
100
+ while (!reader.EndOfStream)
101
+ {
102
+ cancellationToken.ThrowIfCancellationRequested();
103
+ var line = await reader.ReadLineAsync(cancellationToken);
104
+ if (line == null) break;
105
+
106
+ // Forward raw SSE lines
107
+ yield return line;
108
+ }
109
+ }
110
+
111
+ {{else}}
112
+ /// <summary>
113
+ {{#if summary}}
114
+ /// {{summary}}
115
+ {{/if}}
116
+ {{#if description}}
117
+ /// {{description}}
118
+ {{/if}}
119
+ {{#if deprecated}}
120
+ /// [Obsolete] {{#if deprecationMessage}}{{deprecationMessage}}{{/if}}
121
+ {{/if}}
122
+ /// </summary>
123
+ {{#if deprecated}}
124
+ [Obsolete({{#if deprecationMessage}}"{{deprecationMessage}}"{{/if}})]
125
+ {{/if}}
126
+ public async Task<{{#if responseType}}{{responseType}}{{else}}object{{/if}}> {{pascalCase name}}Async(
127
+ {{#each pathParams}}
128
+ {{type}}{{#if (not required)}}?{{/if}} {{name}},
129
+ {{/each}}
130
+ {{#if requestBody}}
131
+ {{requestBody.type}}{{#if (not requestBody.required)}}?{{/if}} body,
132
+ {{/if}}
133
+ {{#if queryType}}
134
+ {{queryType}}? query = null,
135
+ {{/if}}
136
+ CancellationToken cancellationToken = default)
137
+ {
138
+ var path = $"{{path}}"
139
+ {{#each pathParams}}
140
+ .Replace("{{{originalName}}}", Uri.EscapeDataString({{name}}?.ToString() ?? ""))
141
+ {{/each}}
142
+ ;
143
+
144
+ var queryParams = new Dictionary<string, string>();
145
+ {{#if queryType}}
146
+ if (query != null)
147
+ {
148
+ var json = JsonSerializer.Serialize(query);
149
+ var dict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(json);
150
+ if (dict != null)
151
+ {
152
+ foreach (var (key, value) in dict)
153
+ {
154
+ if (value.ValueKind != JsonValueKind.Null)
155
+ queryParams[key] = value.ToString();
156
+ }
157
+ }
158
+ }
159
+ {{/if}}
160
+ {{#if (hasQueryParams this)}}
161
+ {{#each queryParams}}
162
+ if ({{name}} != null)
163
+ queryParams["{{originalName}}"] = {{name}}.ToString();
164
+ {{/each}}
165
+ {{/if}}
166
+
167
+ return await _transport.ExecuteAsync<{{#if responseType}}{{responseType}}{{else}}object{{/if}}>(
168
+ new Transport.Request
169
+ {
170
+ Method = HttpMethod.{{httpMethod}},
171
+ Path = path,
172
+ Query = queryParams.Count > 0 ? queryParams : null,
173
+ {{#if requestBody}}
174
+ Body = body,
175
+ ContentType = "{{requestBody.contentType}}",
176
+ {{/if}}
177
+ },
178
+ cancellationToken);
179
+ }
180
+
181
+ {{/if}}
182
+ {{/each}}
183
+ }
@@ -0,0 +1,45 @@
1
+ namespace {{namespace}};
2
+
3
+ using System;
4
+
5
+ /// <summary>
6
+ /// Represents an error returned by the API.
7
+ /// </summary>
8
+ public class SdkException : Exception
9
+ {
10
+ /// <summary>
11
+ /// HTTP status code.
12
+ /// </summary>
13
+ public int Status { get; }
14
+
15
+ /// <summary>
16
+ /// Machine-readable error code from the API.
17
+ /// </summary>
18
+ public string? Code { get; }
19
+
20
+ /// <summary>
21
+ /// Unique request identifier for support.
22
+ /// </summary>
23
+ public string? RequestId { get; }
24
+
25
+ /// <summary>
26
+ /// Creates a new SdkException.
27
+ /// </summary>
28
+ public SdkException(int status, string? code, string message, string? requestId, Exception? innerException = null)
29
+ : base(message, innerException)
30
+ {
31
+ Status = status;
32
+ Code = code;
33
+ RequestId = requestId;
34
+ }
35
+
36
+ /// <summary>
37
+ /// Returns a string representation of the error.
38
+ /// </summary>
39
+ public override string ToString()
40
+ {
41
+ if (!string.IsNullOrEmpty(Code))
42
+ return $"[{Status}] {Code}: {Message}";
43
+ return $"[{Status}] {Message}";
44
+ }
45
+ }
@@ -0,0 +1,76 @@
1
+ # {{title}} .NET SDK
2
+
3
+ [![NuGet](https://img.shields.io/nuget/v/{{packageName}}.svg)](https://www.nuget.org/packages/{{packageName}}/)
4
+
5
+ The official .NET SDK for {{#if description}}{{description}}{{else}}the {{title}} API{{/if}}.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ dotnet add package {{packageName}}
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```csharp
16
+ using {{namespace}};
17
+
18
+ {{#each securitySchemes}}
19
+ {{#if (eq type "apiKey")}}
20
+ var config = new {{../clientName}}ClientConfig
21
+ {
22
+ {{pascalCase configKey}} = "your-api-key"{{#if (and (eq type "apiKey") (eq in "header"))}}{{/if}}
23
+ };
24
+ {{/if}}
25
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
26
+ var config = new {{../clientName}}ClientConfig
27
+ {
28
+ {{pascalCase configKey}} = "your-bearer-token"
29
+ };
30
+ {{/if}}
31
+ {{/each}}
32
+ {{#unless securitySchemes.length}}
33
+ var config = new {{clientName}}ClientConfig();
34
+ {{/unless}}
35
+
36
+ var client = new {{clientName}}Client(config);
37
+
38
+ {{#each groups}}
39
+ {{#with operations.[0]}}
40
+ // {{../interfaceName}}.{{pascalCase name}} example
41
+ var result = await client.{{pascalCase ../name}}.{{pascalCase name}}Async({{#each ../pathParams}}"{{this}}"{{#unless @last}}, {{/unless}}{{/each}}{{#if requestBody}}{{#if ../pathParams.length}}, {{/if}}new {{requestBody.type}}(){}{{/if}});
42
+ Console.WriteLine(result);
43
+ {{/with}}
44
+ {{/each}}
45
+ ```
46
+
47
+ ## API Reference
48
+
49
+ | Domain | Description |
50
+ |--------|-------------|
51
+ {{#each groups}}
52
+ | `{{interfaceName}}` | {{#if description}}{{description}}{{else}}{{name}} operations{{/if}} |
53
+ {{/each}}
54
+
55
+ ## Error Handling
56
+
57
+ All async methods throw `SdkException` on API errors. Use try-catch to handle errors:
58
+
59
+ ```csharp
60
+ try
61
+ {
62
+ var result = await client.{{#each groups}}{{#if @first}}{{pascalCase name}}{{/if}}{{/each}}.{{#each groups}}{{#if @first}}{{#with operations.[0]}}{{pascalCase name}}Async(){{/with}}{{/if}}{{/each}};
63
+ }
64
+ catch (SdkException ex)
65
+ {
66
+ Console.WriteLine($"Status: {ex.Status}, Code: {ex.Code}, RequestId: {ex.RequestId}");
67
+ }
68
+ ```
69
+
70
+ ## Requirements
71
+
72
+ - .NET 8.0 or later
73
+
74
+ ## License
75
+
76
+ {{#if license}}{{license}}{{else}}MIT{{/if}}
@@ -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}}