@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.
- 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.js +23 -16
- 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,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
|
+
[](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}}
|