@workos/oagen-emitters 0.2.1 → 0.4.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/.husky/pre-commit +1 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +129 -0
- package/dist/index.d.mts +13 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +14549 -3385
- package/dist/index.mjs.map +1 -1
- package/docs/sdk-architecture/dotnet.md +336 -0
- package/docs/sdk-architecture/go.md +338 -0
- package/docs/sdk-architecture/php.md +315 -0
- package/docs/sdk-architecture/python.md +511 -0
- package/oagen.config.ts +328 -2
- package/package.json +9 -5
- package/scripts/generate-php.js +13 -0
- package/scripts/git-push-with-published-oagen.sh +21 -0
- package/smoke/sdk-dotnet.ts +45 -12
- package/smoke/sdk-go.ts +116 -42
- package/smoke/sdk-php.ts +28 -26
- package/smoke/sdk-python.ts +5 -2
- package/src/dotnet/client.ts +89 -0
- package/src/dotnet/enums.ts +323 -0
- package/src/dotnet/fixtures.ts +236 -0
- package/src/dotnet/index.ts +246 -0
- package/src/dotnet/manifest.ts +36 -0
- package/src/dotnet/models.ts +344 -0
- package/src/dotnet/naming.ts +330 -0
- package/src/dotnet/resources.ts +622 -0
- package/src/dotnet/tests.ts +693 -0
- package/src/dotnet/type-map.ts +201 -0
- package/src/dotnet/wrappers.ts +186 -0
- package/src/go/client.ts +141 -0
- package/src/go/enums.ts +196 -0
- package/src/go/fixtures.ts +212 -0
- package/src/go/index.ts +84 -0
- package/src/go/manifest.ts +36 -0
- package/src/go/models.ts +254 -0
- package/src/go/naming.ts +179 -0
- package/src/go/resources.ts +827 -0
- package/src/go/tests.ts +751 -0
- package/src/go/type-map.ts +82 -0
- package/src/go/wrappers.ts +261 -0
- package/src/index.ts +4 -0
- package/src/kotlin/client.ts +53 -0
- package/src/kotlin/enums.ts +162 -0
- package/src/kotlin/index.ts +92 -0
- package/src/kotlin/manifest.ts +55 -0
- package/src/kotlin/models.ts +395 -0
- package/src/kotlin/naming.ts +223 -0
- package/src/kotlin/overrides.ts +25 -0
- package/src/kotlin/resources.ts +667 -0
- package/src/kotlin/tests.ts +1019 -0
- package/src/kotlin/type-map.ts +123 -0
- package/src/kotlin/wrappers.ts +168 -0
- package/src/node/client.ts +128 -115
- package/src/node/enums.ts +9 -0
- package/src/node/errors.ts +37 -232
- package/src/node/field-plan.ts +726 -0
- package/src/node/fixtures.ts +9 -1
- package/src/node/index.ts +3 -9
- package/src/node/models.ts +178 -21
- package/src/node/naming.ts +49 -111
- package/src/node/resources.ts +527 -397
- package/src/node/sdk-errors.ts +41 -0
- package/src/node/tests.ts +69 -19
- package/src/node/type-map.ts +4 -2
- package/src/node/utils.ts +13 -71
- package/src/node/wrappers.ts +151 -0
- package/src/php/client.ts +179 -0
- package/src/php/enums.ts +67 -0
- package/src/php/errors.ts +9 -0
- package/src/php/fixtures.ts +181 -0
- package/src/php/index.ts +96 -0
- package/src/php/manifest.ts +36 -0
- package/src/php/models.ts +310 -0
- package/src/php/naming.ts +279 -0
- package/src/php/resources.ts +636 -0
- package/src/php/tests.ts +609 -0
- package/src/php/type-map.ts +90 -0
- package/src/php/utils.ts +18 -0
- package/src/php/wrappers.ts +152 -0
- package/src/python/client.ts +345 -0
- package/src/python/enums.ts +313 -0
- package/src/python/fixtures.ts +196 -0
- package/src/python/index.ts +95 -0
- package/src/python/manifest.ts +38 -0
- package/src/python/models.ts +688 -0
- package/src/python/naming.ts +189 -0
- package/src/python/resources.ts +1322 -0
- package/src/python/tests.ts +1335 -0
- package/src/python/type-map.ts +93 -0
- package/src/python/wrappers.ts +191 -0
- package/src/shared/model-utils.ts +472 -0
- package/src/shared/naming-utils.ts +154 -0
- package/src/shared/non-spec-services.ts +54 -0
- package/src/shared/resolved-ops.ts +109 -0
- package/src/shared/wrapper-utils.ts +70 -0
- package/test/dotnet/client.test.ts +121 -0
- package/test/dotnet/enums.test.ts +193 -0
- package/test/dotnet/errors.test.ts +9 -0
- package/test/dotnet/manifest.test.ts +82 -0
- package/test/dotnet/models.test.ts +260 -0
- package/test/dotnet/resources.test.ts +255 -0
- package/test/dotnet/tests.test.ts +202 -0
- package/test/go/client.test.ts +92 -0
- package/test/go/enums.test.ts +132 -0
- package/test/go/errors.test.ts +9 -0
- package/test/go/models.test.ts +265 -0
- package/test/go/resources.test.ts +408 -0
- package/test/go/tests.test.ts +143 -0
- package/test/kotlin/models.test.ts +135 -0
- package/test/kotlin/tests.test.ts +176 -0
- package/test/node/client.test.ts +92 -12
- package/test/node/enums.test.ts +2 -0
- package/test/node/errors.test.ts +2 -41
- package/test/node/models.test.ts +2 -0
- package/test/node/naming.test.ts +23 -0
- package/test/node/resources.test.ts +315 -84
- package/test/node/serializers.test.ts +3 -1
- package/test/node/type-map.test.ts +11 -0
- package/test/php/client.test.ts +95 -0
- package/test/php/enums.test.ts +173 -0
- package/test/php/errors.test.ts +9 -0
- package/test/php/models.test.ts +497 -0
- package/test/php/resources.test.ts +682 -0
- package/test/php/tests.test.ts +185 -0
- package/test/python/client.test.ts +200 -0
- package/test/python/enums.test.ts +228 -0
- package/test/python/errors.test.ts +16 -0
- package/test/python/manifest.test.ts +74 -0
- package/test/python/models.test.ts +716 -0
- package/test/python/resources.test.ts +617 -0
- package/test/python/tests.test.ts +202 -0
- package/src/node/common.ts +0 -273
- package/src/node/config.ts +0 -71
- package/src/node/serializers.ts +0 -746
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# C# / .NET SDK Architecture
|
|
2
|
+
|
|
3
|
+
Target: .NET 8.0 | Serialization: Newtonsoft.Json | Test: xUnit + Moq
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
The SDK follows a service-per-domain pattern. A static `WorkOS` entry point holds a singleton `WorkOSClient`. Each service (e.g., `OrganizationsService`) inherits from a shared `Service` base class that lazily resolves the client. All IO is async with `CancellationToken` support.
|
|
8
|
+
|
|
9
|
+
**Runtime files (hand-maintained, `@oagen-ignore-file`):**
|
|
10
|
+
|
|
11
|
+
- `WorkOS.cs` — static entry point
|
|
12
|
+
- `Client/WorkOSClient.cs` — HTTP execution, retry, error translation
|
|
13
|
+
- `Client/_interfaces/WorkOSOptions.cs` — client config
|
|
14
|
+
- `Client/_interfaces/WorkOSRequest.cs` — request DTO
|
|
15
|
+
- `Client/Utilities/RequestUtilities.cs` — JSON/query serialization
|
|
16
|
+
- `Services/Webhooks/WebhookService.cs` — webhook signature verification helper
|
|
17
|
+
- `Services/Webhooks/Entities/Webhook.cs` — webhook event envelope used by the helper
|
|
18
|
+
- `Services/Webhooks/Exceptions/WorkOSWebhookException.cs` — webhook verification exception
|
|
19
|
+
- `Services/_common/Service.cs` — base service class
|
|
20
|
+
- `Services/_common/_interfaces/BaseOptions.cs` — marker base for options
|
|
21
|
+
- `Services/_common/_interfaces/ListOptions.cs` — pagination base options
|
|
22
|
+
- `Services/_common/Entities/WorkOSList.cs` — pagination wrapper
|
|
23
|
+
- `Services/_common/Entities/ListMetadata.cs` — cursor metadata
|
|
24
|
+
- `Services/_common/Enums/PaginationOrder.cs` — asc/desc enum
|
|
25
|
+
|
|
26
|
+
**Generated files (emitter output):**
|
|
27
|
+
|
|
28
|
+
- `Services/{Mount}/Entities/*.cs` — model classes
|
|
29
|
+
- `Services/{Mount}/Enums/*.cs` — enum types
|
|
30
|
+
- `Services/{Mount}/{Mount}Service.cs` — service class with methods
|
|
31
|
+
- `Services/{Mount}/_interfaces/*Options.cs` — request option classes
|
|
32
|
+
- `test/WorkOSTests/Tests/*Test.cs` — generated service tests
|
|
33
|
+
- `test/WorkOSTests/testdata/*.json` — generated fixtures
|
|
34
|
+
- `test/WorkOSTests/xunit.runner.json` — generated xUnit runner config
|
|
35
|
+
|
|
36
|
+
## Naming Conventions
|
|
37
|
+
|
|
38
|
+
| IR Concept | C# Convention | Example |
|
|
39
|
+
| -------------- | ---------------------------- | --------------------------- |
|
|
40
|
+
| Model name | PascalCase | `Organization` |
|
|
41
|
+
| Enum name | PascalCase | `ConnectionState` |
|
|
42
|
+
| Field/property | PascalCase | `EmailVerified` |
|
|
43
|
+
| Method | PascalCase (no Async suffix) | `GetOrganization` |
|
|
44
|
+
| File | PascalCase.cs | `Organization.cs` |
|
|
45
|
+
| Service class | `{Mount}Service` | `OrganizationsService` |
|
|
46
|
+
| Options class | `{Action}{Entity}Options` | `CreateOrganizationOptions` |
|
|
47
|
+
| Namespace | `WorkOS` | — |
|
|
48
|
+
|
|
49
|
+
## Type Mapping
|
|
50
|
+
|
|
51
|
+
| IR TypeRef | C# Type |
|
|
52
|
+
| ------------------------------ | ----------------------- |
|
|
53
|
+
| `primitive:string` | `string` |
|
|
54
|
+
| `primitive:string` (date-time) | `string` |
|
|
55
|
+
| `primitive:string` (uuid) | `string` |
|
|
56
|
+
| `primitive:string` (binary) | `byte[]` |
|
|
57
|
+
| `primitive:integer` | `int` |
|
|
58
|
+
| `primitive:integer` (int64) | `long` |
|
|
59
|
+
| `primitive:number` | `double` |
|
|
60
|
+
| `primitive:boolean` | `bool` |
|
|
61
|
+
| `primitive:unknown` | `object` |
|
|
62
|
+
| `model:Foo` | `Foo` (reference type) |
|
|
63
|
+
| `enum:Foo` | `Foo` (value type) |
|
|
64
|
+
| `array` | `List<T>` |
|
|
65
|
+
| `map` | `Dictionary<string, T>` |
|
|
66
|
+
| `nullable` (value type) | `T?` |
|
|
67
|
+
| `nullable` (reference type) | `T` |
|
|
68
|
+
| `union` (single) | that type |
|
|
69
|
+
| `union` (multiple) | `object` |
|
|
70
|
+
|
|
71
|
+
## Model Pattern
|
|
72
|
+
|
|
73
|
+
```csharp
|
|
74
|
+
namespace WorkOS
|
|
75
|
+
{
|
|
76
|
+
using Newtonsoft.Json;
|
|
77
|
+
|
|
78
|
+
/// <summary>Represents an organization.</summary>
|
|
79
|
+
public class Organization
|
|
80
|
+
{
|
|
81
|
+
[JsonProperty("id")]
|
|
82
|
+
public string Id { get; set; }
|
|
83
|
+
|
|
84
|
+
[JsonProperty("name")]
|
|
85
|
+
public string Name { get; set; }
|
|
86
|
+
|
|
87
|
+
[JsonProperty("allow_profiles_outside_organization")]
|
|
88
|
+
public bool AllowProfilesOutsideOrganization { get; set; }
|
|
89
|
+
|
|
90
|
+
[JsonProperty("created_at")]
|
|
91
|
+
public string CreatedAt { get; set; }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Enum Pattern
|
|
97
|
+
|
|
98
|
+
```csharp
|
|
99
|
+
namespace WorkOS
|
|
100
|
+
{
|
|
101
|
+
using System.Runtime.Serialization;
|
|
102
|
+
using Newtonsoft.Json;
|
|
103
|
+
using Newtonsoft.Json.Converters;
|
|
104
|
+
|
|
105
|
+
[JsonConverter(typeof(StringEnumConverter))]
|
|
106
|
+
public enum OrganizationDomainState
|
|
107
|
+
{
|
|
108
|
+
[EnumMember(Value = "failed")]
|
|
109
|
+
Failed,
|
|
110
|
+
|
|
111
|
+
[EnumMember(Value = "pending")]
|
|
112
|
+
Pending,
|
|
113
|
+
|
|
114
|
+
[EnumMember(Value = "verified")]
|
|
115
|
+
Verified,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Resource/Service Pattern
|
|
121
|
+
|
|
122
|
+
```csharp
|
|
123
|
+
namespace WorkOS
|
|
124
|
+
{
|
|
125
|
+
using System.Net.Http;
|
|
126
|
+
using System.Threading;
|
|
127
|
+
using System.Threading.Tasks;
|
|
128
|
+
|
|
129
|
+
public class OrganizationsService : Service
|
|
130
|
+
{
|
|
131
|
+
public OrganizationsService() { }
|
|
132
|
+
public OrganizationsService(WorkOSClient client) : base(client) { }
|
|
133
|
+
|
|
134
|
+
public async Task<Organization> GetOrganization(
|
|
135
|
+
string id,
|
|
136
|
+
CancellationToken cancellationToken = default)
|
|
137
|
+
{
|
|
138
|
+
var request = new WorkOSRequest
|
|
139
|
+
{
|
|
140
|
+
Method = HttpMethod.Get,
|
|
141
|
+
Path = $"/organizations/{id}",
|
|
142
|
+
};
|
|
143
|
+
return await this.Client.MakeAPIRequest<Organization>(request, cancellationToken);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
public async Task<WorkOSList<Organization>> ListOrganizations(
|
|
147
|
+
ListOrganizationsOptions options = null,
|
|
148
|
+
CancellationToken cancellationToken = default)
|
|
149
|
+
{
|
|
150
|
+
var request = new WorkOSRequest
|
|
151
|
+
{
|
|
152
|
+
Method = HttpMethod.Get,
|
|
153
|
+
Path = "/organizations",
|
|
154
|
+
Options = options,
|
|
155
|
+
};
|
|
156
|
+
return await this.Client.MakeAPIRequest<WorkOSList<Organization>>(request, cancellationToken);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public async Task<Organization> CreateOrganization(
|
|
160
|
+
CreateOrganizationOptions options,
|
|
161
|
+
CancellationToken cancellationToken = default)
|
|
162
|
+
{
|
|
163
|
+
var request = new WorkOSRequest
|
|
164
|
+
{
|
|
165
|
+
Method = HttpMethod.Post,
|
|
166
|
+
Path = "/organizations",
|
|
167
|
+
Options = options,
|
|
168
|
+
};
|
|
169
|
+
return await this.Client.MakeAPIRequest<Organization>(request, cancellationToken);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
public async Task DeleteOrganization(
|
|
173
|
+
string id,
|
|
174
|
+
CancellationToken cancellationToken = default)
|
|
175
|
+
{
|
|
176
|
+
var request = new WorkOSRequest
|
|
177
|
+
{
|
|
178
|
+
Method = HttpMethod.Delete,
|
|
179
|
+
Path = $"/organizations/{id}",
|
|
180
|
+
};
|
|
181
|
+
await this.Client.MakeRawAPIRequest(request, cancellationToken);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Options Pattern
|
|
188
|
+
|
|
189
|
+
```csharp
|
|
190
|
+
namespace WorkOS
|
|
191
|
+
{
|
|
192
|
+
using Newtonsoft.Json;
|
|
193
|
+
|
|
194
|
+
public class CreateOrganizationOptions : BaseOptions
|
|
195
|
+
{
|
|
196
|
+
[JsonProperty("name")]
|
|
197
|
+
public string Name { get; set; }
|
|
198
|
+
|
|
199
|
+
[JsonProperty("domain_data")]
|
|
200
|
+
public List<OrganizationDomainDataOptions> DomainData { get; set; }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public class ListOrganizationsOptions : ListOptions
|
|
204
|
+
{
|
|
205
|
+
[JsonProperty("domains")]
|
|
206
|
+
public string[] Domains { get; set; }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Pagination Pattern
|
|
212
|
+
|
|
213
|
+
```csharp
|
|
214
|
+
// Returned by list methods — NOT an iterator
|
|
215
|
+
public class WorkOSList<T>
|
|
216
|
+
{
|
|
217
|
+
[JsonProperty("data")]
|
|
218
|
+
public List<T> Data { get; set; }
|
|
219
|
+
|
|
220
|
+
[JsonProperty("list_metadata")]
|
|
221
|
+
public ListMetadata ListMetadata { get; set; }
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Error Handling
|
|
226
|
+
|
|
227
|
+
The runtime translates HTTP status codes to SDK-native exceptions:
|
|
228
|
+
|
|
229
|
+
- `AuthenticationError` (401)
|
|
230
|
+
- `NotFoundError` (404)
|
|
231
|
+
- `UnprocessableEntityError` (422)
|
|
232
|
+
- `RateLimitExceededError` (429)
|
|
233
|
+
- `ServerError` (500+)
|
|
234
|
+
|
|
235
|
+
These are hand-maintained in the runtime. The emitter generates error-handling _tests_, not the error classes themselves.
|
|
236
|
+
|
|
237
|
+
## Hidden Parameter Injection
|
|
238
|
+
|
|
239
|
+
For operations with defaults/inferFromClient, the service method sets properties on the options before making the request:
|
|
240
|
+
|
|
241
|
+
```csharp
|
|
242
|
+
public async Task<AuthenticationResponse> AuthenticateWithPassword(
|
|
243
|
+
AuthenticateWithPasswordOptions options,
|
|
244
|
+
CancellationToken cancellationToken = default)
|
|
245
|
+
{
|
|
246
|
+
options.GrantType = "password";
|
|
247
|
+
options.ClientId = this.Client.ClientId;
|
|
248
|
+
options.ClientSecret = this.Client.ApiKey;
|
|
249
|
+
var request = new WorkOSRequest
|
|
250
|
+
{
|
|
251
|
+
Method = HttpMethod.Post,
|
|
252
|
+
Path = "/user_management/authenticate",
|
|
253
|
+
Options = options,
|
|
254
|
+
};
|
|
255
|
+
return await this.Client.MakeAPIRequest<AuthenticationResponse>(request, cancellationToken);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Testing Pattern
|
|
260
|
+
|
|
261
|
+
xUnit + Moq with HttpMock utility:
|
|
262
|
+
|
|
263
|
+
```csharp
|
|
264
|
+
public class OrganizationsServiceTest
|
|
265
|
+
{
|
|
266
|
+
private readonly HttpMock httpMock;
|
|
267
|
+
private readonly OrganizationsService service;
|
|
268
|
+
|
|
269
|
+
public OrganizationsServiceTest()
|
|
270
|
+
{
|
|
271
|
+
this.httpMock = new HttpMock();
|
|
272
|
+
var client = new WorkOSClient(new WorkOSOptions
|
|
273
|
+
{
|
|
274
|
+
ApiKey = "sk_test",
|
|
275
|
+
HttpClient = this.httpMock.HttpClient,
|
|
276
|
+
});
|
|
277
|
+
this.service = new OrganizationsService(client);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
[Fact]
|
|
281
|
+
public async Task TestGetOrganization()
|
|
282
|
+
{
|
|
283
|
+
var fixture = File.ReadAllText("testdata/organization.json");
|
|
284
|
+
this.httpMock.MockResponse(HttpMethod.Get, "/organizations/org_01234", HttpStatusCode.OK, fixture);
|
|
285
|
+
var result = await this.service.GetOrganization("org_01234");
|
|
286
|
+
Assert.NotNull(result);
|
|
287
|
+
Assert.Equal("org_01234", result.Id);
|
|
288
|
+
this.httpMock.AssertRequestWasMade(HttpMethod.Get, "/organizations/org_01234");
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Structural Guidelines
|
|
294
|
+
|
|
295
|
+
| Category | Choice |
|
|
296
|
+
| ------------------ | --------------------------- |
|
|
297
|
+
| Target Framework | .NET 8.0 |
|
|
298
|
+
| HTTP Client | System.Net.Http.HttpClient |
|
|
299
|
+
| JSON Parsing | Newtonsoft.Json 13.x |
|
|
300
|
+
| Testing Framework | xUnit 2.x |
|
|
301
|
+
| HTTP Mocking | Moq 4.x (HttpClientHandler) |
|
|
302
|
+
| Linting/Formatting | StyleCop.Analyzers |
|
|
303
|
+
| Package Manager | NuGet |
|
|
304
|
+
| Build Tool | dotnet CLI / MSBuild |
|
|
305
|
+
|
|
306
|
+
## Directory Structure
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
src/WorkOS.net/
|
|
310
|
+
├── WorkOS.cs # @oagen-ignore-file
|
|
311
|
+
├── Client/ # @oagen-ignore-file (all)
|
|
312
|
+
│ ├── WorkOSClient.cs
|
|
313
|
+
│ ├── _interfaces/
|
|
314
|
+
│ └── Utilities/
|
|
315
|
+
├── Services/
|
|
316
|
+
│ ├── _common/ # @oagen-ignore-file (all)
|
|
317
|
+
│ ├── Webhooks/ # Mixed hand-written + generated
|
|
318
|
+
│ │ ├── Entities/Webhook.cs # @oagen-ignore-file
|
|
319
|
+
│ │ ├── Exceptions/WorkOSWebhookException.cs
|
|
320
|
+
│ │ ├── WebhookService.cs # @oagen-ignore-file
|
|
321
|
+
│ │ └── WebhooksService.cs # Generated
|
|
322
|
+
│ └── {ServiceName}/ # Generated
|
|
323
|
+
│ ├── {ServiceName}Service.cs
|
|
324
|
+
│ ├── _interfaces/
|
|
325
|
+
│ │ └── {Action}{Entity}Options.cs
|
|
326
|
+
│ ├── Entities/
|
|
327
|
+
│ │ └── {Entity}.cs
|
|
328
|
+
│ └── Enums/
|
|
329
|
+
│ └── {EnumName}.cs
|
|
330
|
+
test/WorkOSTests/
|
|
331
|
+
├── Utilities/HttpMock.cs # @oagen-ignore-file
|
|
332
|
+
├── Client/ # @oagen-ignore-file
|
|
333
|
+
├── Services/Webhooks/WebhookTests.cs # @oagen-ignore-file
|
|
334
|
+
├── Tests/{ServiceName}Test.cs # Generated
|
|
335
|
+
└── xunit.runner.json # Generated
|
|
336
|
+
```
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Go SDK Architecture
|
|
2
|
+
|
|
3
|
+
Scenario B (fresh) -- no backwards-compatibility constraints.
|
|
4
|
+
Reference: stripe/stripe-go for idiomatic Go patterns.
|
|
5
|
+
|
|
6
|
+
## Architecture Overview
|
|
7
|
+
|
|
8
|
+
Single flat `workos` package. All types, services, and the client live in one package,
|
|
9
|
+
accessed as `workos.Organization`, `workos.NewClient(...)`, etc.
|
|
10
|
+
|
|
11
|
+
- **Client**: Instance-scoped via `NewClient(apiKey, ...Option)`
|
|
12
|
+
- **Services**: Unexported structs with exported methods, accessed as fields on Client
|
|
13
|
+
- **Models**: Exported structs with `json:"snake_case"` tags
|
|
14
|
+
- **Enums**: Typed `string` constants (no iota)
|
|
15
|
+
- **Params**: `*Params` structs with embedded `RequestOptions` for per-request overrides
|
|
16
|
+
- **Errors**: SDK-native error types implementing `error` interface
|
|
17
|
+
- **Pagination**: Iterator with `Next()` / `Current()` / `Err()`
|
|
18
|
+
|
|
19
|
+
## Naming Conventions
|
|
20
|
+
|
|
21
|
+
| IR Name | Go Name | Context |
|
|
22
|
+
| -------------------------- | --------------------- | ---------------------------------------- |
|
|
23
|
+
| `Organization` (model) | `Organization` | Struct type |
|
|
24
|
+
| `organization` (file) | `organization.go` | File name |
|
|
25
|
+
| `listUsers` (method) | `ListUsers` | Exported method |
|
|
26
|
+
| `user_id` (field) | `UserID` | Struct field (PascalCase, acronym-aware) |
|
|
27
|
+
| `Status` (enum) | `Status` | Type declaration |
|
|
28
|
+
| `active` (enum value) | `StatusActive` | Const: `{TypeName}{PascalValue}` |
|
|
29
|
+
| `Organizations` (service) | `organizationService` | Unexported struct |
|
|
30
|
+
| `organizations` (accessor) | `Organizations()` | Client method returning service |
|
|
31
|
+
|
|
32
|
+
### Acronym handling
|
|
33
|
+
|
|
34
|
+
Go convention preserves full-caps for common acronyms: `ID`, `URL`, `SSO`, `API`, `HTTP`,
|
|
35
|
+
`JWT`, `MFA`, `CORS`, `SAML`, `SCIM`, `RBAC`, `OAuth`, `OIDC`, `UUID`, `JSON`, `HTML`.
|
|
36
|
+
|
|
37
|
+
## Type Mapping
|
|
38
|
+
|
|
39
|
+
| IR TypeRef | Go Type |
|
|
40
|
+
| ---------------------------- | --------------------------------- |
|
|
41
|
+
| `primitive:string` | `string` |
|
|
42
|
+
| `primitive:string:date` | `string` |
|
|
43
|
+
| `primitive:string:date-time` | `string` |
|
|
44
|
+
| `primitive:string:uuid` | `string` |
|
|
45
|
+
| `primitive:string:binary` | `[]byte` |
|
|
46
|
+
| `primitive:integer` | `int` |
|
|
47
|
+
| `primitive:number` | `float64` |
|
|
48
|
+
| `primitive:boolean` | `bool` |
|
|
49
|
+
| `primitive:unknown` | `interface{}` |
|
|
50
|
+
| `array` | `[]T` |
|
|
51
|
+
| `model` | `*ModelName` (pointer for nested) |
|
|
52
|
+
| `enum` | `EnumType` |
|
|
53
|
+
| `nullable` | `*T` (pointer) |
|
|
54
|
+
| `union` | `interface{}` |
|
|
55
|
+
| `map` | `map[string]T` |
|
|
56
|
+
| `literal:string` | `string` |
|
|
57
|
+
| `literal:null` | `interface{}` |
|
|
58
|
+
|
|
59
|
+
## Model Pattern
|
|
60
|
+
|
|
61
|
+
```go
|
|
62
|
+
// Organization represents an organization.
|
|
63
|
+
type Organization struct {
|
|
64
|
+
ID string `json:"id"`
|
|
65
|
+
Name string `json:"name"`
|
|
66
|
+
Domains []string `json:"domains"`
|
|
67
|
+
Metadata map[string]string `json:"metadata,omitempty"`
|
|
68
|
+
CreatedAt string `json:"created_at"`
|
|
69
|
+
UpdatedAt string `json:"updated_at"`
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- Required fields: value types (no pointer)
|
|
74
|
+
- Optional fields: pointer types with `omitempty`
|
|
75
|
+
- Nested models: always pointers
|
|
76
|
+
- JSON tags: snake_case matching the API wire format
|
|
77
|
+
|
|
78
|
+
## Enum Pattern
|
|
79
|
+
|
|
80
|
+
```go
|
|
81
|
+
// OrganizationStatus represents the status of an organization.
|
|
82
|
+
type OrganizationStatus string
|
|
83
|
+
|
|
84
|
+
const (
|
|
85
|
+
OrganizationStatusActive OrganizationStatus = "active"
|
|
86
|
+
OrganizationStatusInactive OrganizationStatus = "inactive"
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Resource/Service Pattern
|
|
91
|
+
|
|
92
|
+
```go
|
|
93
|
+
type organizationService struct {
|
|
94
|
+
client *Client
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ListOrganizations lists all organizations.
|
|
98
|
+
func (s *organizationService) ListOrganizations(
|
|
99
|
+
ctx context.Context,
|
|
100
|
+
params *ListOrganizationsParams,
|
|
101
|
+
opts ...RequestOption,
|
|
102
|
+
) *Iterator[Organization] {
|
|
103
|
+
// ...
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// GetOrganization retrieves an organization by ID.
|
|
107
|
+
func (s *organizationService) GetOrganization(
|
|
108
|
+
ctx context.Context,
|
|
109
|
+
id string,
|
|
110
|
+
opts ...RequestOption,
|
|
111
|
+
) (*Organization, error) {
|
|
112
|
+
// ...
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// CreateOrganization creates a new organization.
|
|
116
|
+
func (s *organizationService) CreateOrganization(
|
|
117
|
+
ctx context.Context,
|
|
118
|
+
params *CreateOrganizationParams,
|
|
119
|
+
opts ...RequestOption,
|
|
120
|
+
) (*Organization, error) {
|
|
121
|
+
// ...
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// DeleteOrganization deletes an organization by ID.
|
|
125
|
+
func (s *organizationService) DeleteOrganization(
|
|
126
|
+
ctx context.Context,
|
|
127
|
+
id string,
|
|
128
|
+
opts ...RequestOption,
|
|
129
|
+
) error {
|
|
130
|
+
// ...
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Parameter Structs
|
|
135
|
+
|
|
136
|
+
```go
|
|
137
|
+
type CreateOrganizationParams struct {
|
|
138
|
+
Name string `json:"name"`
|
|
139
|
+
Domains []string `json:"domains,omitempty"`
|
|
140
|
+
Metadata map[string]string `json:"metadata,omitempty"`
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
type ListOrganizationsParams struct {
|
|
144
|
+
After *string `url:"after,omitempty"`
|
|
145
|
+
Before *string `url:"before,omitempty"`
|
|
146
|
+
Limit *int `url:"limit,omitempty"`
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Path parameters
|
|
151
|
+
|
|
152
|
+
Path parameters (IDs etc.) are positional function arguments, not part of params structs.
|
|
153
|
+
|
|
154
|
+
## Client Pattern
|
|
155
|
+
|
|
156
|
+
```go
|
|
157
|
+
// Client is the WorkOS API client.
|
|
158
|
+
type Client struct {
|
|
159
|
+
apiKey string
|
|
160
|
+
baseURL string
|
|
161
|
+
httpClient *http.Client
|
|
162
|
+
maxRetries int
|
|
163
|
+
// Service accessors
|
|
164
|
+
organizations *organizationService
|
|
165
|
+
users *userService
|
|
166
|
+
// ...
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// NewClient creates a new WorkOS client.
|
|
170
|
+
func NewClient(apiKey string, opts ...ClientOption) *Client {
|
|
171
|
+
c := &Client{
|
|
172
|
+
apiKey: apiKey,
|
|
173
|
+
baseURL: "https://api.workos.com",
|
|
174
|
+
httpClient: &http.Client{Timeout: 60 * time.Second},
|
|
175
|
+
maxRetries: 3,
|
|
176
|
+
}
|
|
177
|
+
for _, opt := range opts {
|
|
178
|
+
opt(c)
|
|
179
|
+
}
|
|
180
|
+
c.organizations = &organizationService{client: c}
|
|
181
|
+
c.users = &userService{client: c}
|
|
182
|
+
return c
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Organizations returns the organizations service.
|
|
186
|
+
func (c *Client) Organizations() *organizationService {
|
|
187
|
+
return c.organizations
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Functional Options
|
|
192
|
+
|
|
193
|
+
```go
|
|
194
|
+
type ClientOption func(*Client)
|
|
195
|
+
|
|
196
|
+
func WithBaseURL(url string) ClientOption {
|
|
197
|
+
return func(c *Client) { c.baseURL = url }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
func WithHTTPClient(client *http.Client) ClientOption {
|
|
201
|
+
return func(c *Client) { c.httpClient = client }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func WithMaxRetries(n int) ClientOption {
|
|
205
|
+
return func(c *Client) { c.maxRetries = n }
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Per-Request Options
|
|
210
|
+
|
|
211
|
+
```go
|
|
212
|
+
type RequestOption func(*requestConfig)
|
|
213
|
+
|
|
214
|
+
type requestConfig struct {
|
|
215
|
+
extraHeaders http.Header
|
|
216
|
+
timeout time.Duration
|
|
217
|
+
maxRetries *int
|
|
218
|
+
baseURL string
|
|
219
|
+
idempotencyKey string
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func WithExtraHeaders(h http.Header) RequestOption {
|
|
223
|
+
return func(r *requestConfig) { r.extraHeaders = h }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
func WithTimeout(d time.Duration) RequestOption {
|
|
227
|
+
return func(r *requestConfig) { r.timeout = d }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
func WithIdempotencyKey(key string) RequestOption {
|
|
231
|
+
return func(r *requestConfig) { r.idempotencyKey = key }
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Pagination Pattern
|
|
236
|
+
|
|
237
|
+
```go
|
|
238
|
+
// Iterator provides auto-pagination over list endpoints.
|
|
239
|
+
type Iterator[T any] struct {
|
|
240
|
+
cur *T
|
|
241
|
+
items []T
|
|
242
|
+
err error
|
|
243
|
+
params listParams
|
|
244
|
+
fetcher func(context.Context, listParams) (*listResponse[T], error)
|
|
245
|
+
ctx context.Context
|
|
246
|
+
done bool
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Next advances the iterator. Returns false when done or on error.
|
|
250
|
+
func (it *Iterator[T]) Next() bool { ... }
|
|
251
|
+
|
|
252
|
+
// Current returns the current item.
|
|
253
|
+
func (it *Iterator[T]) Current() *T { return it.cur }
|
|
254
|
+
|
|
255
|
+
// Err returns any error from the last page fetch.
|
|
256
|
+
func (it *Iterator[T]) Err() error { return it.err }
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Error Handling
|
|
260
|
+
|
|
261
|
+
```go
|
|
262
|
+
type APIError struct {
|
|
263
|
+
StatusCode int `json:"-"`
|
|
264
|
+
RequestID string `json:"-"`
|
|
265
|
+
Code string `json:"code"`
|
|
266
|
+
Message string `json:"message"`
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
func (e *APIError) Error() string {
|
|
270
|
+
return fmt.Sprintf("workos: %d %s: %s", e.StatusCode, e.Code, e.Message)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Sentinel types for errors.Is() / type assertions
|
|
274
|
+
type AuthenticationError struct{ *APIError }
|
|
275
|
+
type NotFoundError struct{ *APIError }
|
|
276
|
+
type RateLimitExceededError struct{ *APIError }
|
|
277
|
+
type UnprocessableEntityError struct{ *APIError }
|
|
278
|
+
type ServerError struct{ *APIError }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Retry Logic
|
|
282
|
+
|
|
283
|
+
- Exponential backoff with jitter: `min(base * 2^attempt + jitter, maxDelay)`
|
|
284
|
+
- Retryable statuses: 429, 500, 502, 503, 504
|
|
285
|
+
- Respect `Retry-After` header
|
|
286
|
+
- Auto-generate UUID idempotency key for POST requests, reused across retries
|
|
287
|
+
- Default max retries: 3
|
|
288
|
+
|
|
289
|
+
## Test Pattern
|
|
290
|
+
|
|
291
|
+
```go
|
|
292
|
+
func TestOrganizations_GetOrganization(t *testing.T) {
|
|
293
|
+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
294
|
+
require.Equal(t, http.MethodGet, r.Method)
|
|
295
|
+
require.Equal(t, "/organizations/org_123", r.URL.Path)
|
|
296
|
+
w.Header().Set("Content-Type", "application/json")
|
|
297
|
+
w.WriteHeader(http.StatusOK)
|
|
298
|
+
fixture, _ := os.ReadFile("testdata/organization.json")
|
|
299
|
+
w.Write(fixture)
|
|
300
|
+
}))
|
|
301
|
+
defer server.Close()
|
|
302
|
+
|
|
303
|
+
client := workos.NewClient("sk_test", workos.WithBaseURL(server.URL))
|
|
304
|
+
org, err := client.Organizations().GetOrganization(context.Background(), "org_123")
|
|
305
|
+
require.NoError(t, err)
|
|
306
|
+
require.Equal(t, "org_123", org.ID)
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Structural Guidelines
|
|
311
|
+
|
|
312
|
+
| Category | Choice |
|
|
313
|
+
| ------------------ | ------------------------------------------------- |
|
|
314
|
+
| Testing Framework | `testing` + `github.com/stretchr/testify/require` |
|
|
315
|
+
| HTTP Mocking | `net/http/httptest` |
|
|
316
|
+
| Documentation | GoDoc comments |
|
|
317
|
+
| Type Signatures | Native Go types (inline) |
|
|
318
|
+
| Linting/Formatting | `gofmt` / `go vet` |
|
|
319
|
+
| HTTP Client | `net/http` (stdlib) |
|
|
320
|
+
| JSON Parsing | `encoding/json` (stdlib) |
|
|
321
|
+
| Package Manager | Go modules (`go.mod`) |
|
|
322
|
+
| Build Tool | `go build` / `go test` |
|
|
323
|
+
|
|
324
|
+
## Directory Structure (generated SDK)
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
workos-go/
|
|
328
|
+
+-- go.mod
|
|
329
|
+
+-- go.sum
|
|
330
|
+
+-- workos.go // Package doc, NewClient, ClientOption, RequestOption
|
|
331
|
+
+-- client.go // Client struct, HTTP request execution, retry logic
|
|
332
|
+
+-- errors.go // Error types
|
|
333
|
+
+-- pagination.go // Iterator[T]
|
|
334
|
+
+-- {service}.go // Models, enums, params, service client for each service
|
|
335
|
+
+-- {service}_test.go // Tests for each service
|
|
336
|
+
+-- testdata/
|
|
337
|
+
| +-- {model}.json // JSON fixtures
|
|
338
|
+
```
|