@typespec/http-server-csharp 0.58.0-alpha.8-dev.2 → 0.58.0-alpha.8-dev.3
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/cmd/hscs.js +2 -0
- package/dist/src/cli/cli.d.ts +2 -0
- package/dist/src/cli/cli.d.ts.map +1 -0
- package/dist/src/cli/cli.js +124 -0
- package/dist/src/cli/cli.js.map +1 -0
- package/dist/src/lib/attributes.d.ts.map +1 -0
- package/dist/src/lib/attributes.js.map +1 -0
- package/dist/src/{boilerplate.d.ts → lib/boilerplate.d.ts} +2 -0
- package/dist/src/lib/boilerplate.d.ts.map +1 -0
- package/dist/src/{boilerplate.js → lib/boilerplate.js} +101 -36
- package/dist/src/lib/boilerplate.js.map +1 -0
- package/dist/src/lib/index.d.ts.map +1 -0
- package/dist/src/lib/index.js.map +1 -0
- package/dist/src/{interfaces.d.ts → lib/interfaces.d.ts} +2 -0
- package/dist/src/lib/interfaces.d.ts.map +1 -0
- package/dist/src/{interfaces.js → lib/interfaces.js} +3 -1
- package/dist/src/lib/interfaces.js.map +1 -0
- package/dist/src/{lib.d.ts → lib/lib.d.ts} +6 -0
- package/dist/src/lib/lib.d.ts.map +1 -0
- package/dist/src/{lib.js → lib/lib.js} +19 -0
- package/dist/src/lib/lib.js.map +1 -0
- package/dist/src/lib/scaffolding.d.ts +20 -0
- package/dist/src/lib/scaffolding.d.ts.map +1 -0
- package/dist/src/lib/scaffolding.js +388 -0
- package/dist/src/lib/scaffolding.js.map +1 -0
- package/dist/src/lib/service.d.ts.map +1 -0
- package/dist/src/{service.js → lib/service.js} +78 -18
- package/dist/src/lib/service.js.map +1 -0
- package/dist/src/lib/testing/index.d.ts.map +1 -0
- package/dist/src/{testing → lib/testing}/index.js +1 -0
- package/dist/src/lib/testing/index.js.map +1 -0
- package/dist/src/lib/type-helpers.d.ts.map +1 -0
- package/dist/src/lib/type-helpers.js.map +1 -0
- package/dist/src/lib/utils.d.ts.map +1 -0
- package/dist/src/lib/utils.js.map +1 -0
- package/package.json +20 -11
- package/dist/src/attributes.d.ts.map +0 -1
- package/dist/src/attributes.js.map +0 -1
- package/dist/src/boilerplate.d.ts.map +0 -1
- package/dist/src/boilerplate.js.map +0 -1
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js.map +0 -1
- package/dist/src/interfaces.d.ts.map +0 -1
- package/dist/src/interfaces.js.map +0 -1
- package/dist/src/lib.d.ts.map +0 -1
- package/dist/src/lib.js.map +0 -1
- package/dist/src/service.d.ts.map +0 -1
- package/dist/src/service.js.map +0 -1
- package/dist/src/testing/index.d.ts.map +0 -1
- package/dist/src/testing/index.js.map +0 -1
- package/dist/src/type-helpers.d.ts.map +0 -1
- package/dist/src/type-helpers.js.map +0 -1
- package/dist/src/utils.d.ts.map +0 -1
- package/dist/src/utils.js.map +0 -1
- /package/dist/src/{attributes.d.ts → lib/attributes.d.ts} +0 -0
- /package/dist/src/{attributes.js → lib/attributes.js} +0 -0
- /package/dist/src/{index.d.ts → lib/index.d.ts} +0 -0
- /package/dist/src/{index.js → lib/index.js} +0 -0
- /package/dist/src/{service.d.ts → lib/service.d.ts} +0 -0
- /package/dist/src/{testing → lib/testing}/index.d.ts +0 -0
- /package/dist/src/{type-helpers.d.ts → lib/type-helpers.d.ts} +0 -0
- /package/dist/src/{type-helpers.js → lib/type-helpers.js} +0 -0
- /package/dist/src/{utils.d.ts → lib/utils.d.ts} +0 -0
- /package/dist/src/{utils.js → lib/utils.js} +0 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { code } from "@typespec/compiler/emitter-framework";
|
|
2
|
+
import { GeneratedFileHeaderWithNullable } from "./boilerplate.js";
|
|
3
|
+
import { LibrarySourceFile } from "./interfaces.js";
|
|
4
|
+
export function getScaffoldingHelpers(emitter, useSwagger, openApiPath) {
|
|
5
|
+
const sourceFiles = [];
|
|
6
|
+
sourceFiles.push(new LibrarySourceFile({
|
|
7
|
+
filename: "IInitializer.cs",
|
|
8
|
+
emitter: emitter,
|
|
9
|
+
getContents: getInitializerInterface,
|
|
10
|
+
}), new LibrarySourceFile({
|
|
11
|
+
filename: "Initializer.cs",
|
|
12
|
+
emitter: emitter,
|
|
13
|
+
getContents: getInitializerImplementation,
|
|
14
|
+
}), new LibrarySourceFile({
|
|
15
|
+
filename: "Program.cs",
|
|
16
|
+
emitter: emitter,
|
|
17
|
+
getContents: () => getProjectStartup(useSwagger, openApiPath),
|
|
18
|
+
path: "../",
|
|
19
|
+
}));
|
|
20
|
+
return sourceFiles;
|
|
21
|
+
}
|
|
22
|
+
export function getBusinessLogicImplementations(emitter, registrations) {
|
|
23
|
+
const sourceFiles = [];
|
|
24
|
+
const mocks = [];
|
|
25
|
+
for (const [name, impl] of registrations) {
|
|
26
|
+
sourceFiles.push(new LibrarySourceFile({
|
|
27
|
+
filename: `${impl.className}.cs`,
|
|
28
|
+
emitter: emitter,
|
|
29
|
+
getContents: () => getBusinessLogicImplementation(name, impl),
|
|
30
|
+
path: "../mocks/",
|
|
31
|
+
}));
|
|
32
|
+
mocks.push(impl);
|
|
33
|
+
}
|
|
34
|
+
if (mocks.length > 0) {
|
|
35
|
+
sourceFiles.push(new LibrarySourceFile({
|
|
36
|
+
filename: "MockRegistration.cs",
|
|
37
|
+
emitter: emitter,
|
|
38
|
+
getContents: () => getMockRegistration(mocks),
|
|
39
|
+
path: "../mocks/",
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
return sourceFiles;
|
|
43
|
+
}
|
|
44
|
+
function getBusinessLogicImplementation(iface, mock) {
|
|
45
|
+
const methods = [];
|
|
46
|
+
for (const method of mock.methods) {
|
|
47
|
+
const methodCode = method.instantiatedReturnType !== undefined
|
|
48
|
+
? `return Task.FromResult(_initializer.Initialize<${method.instantiatedReturnType}>());`
|
|
49
|
+
: "return Task.CompletedTask;";
|
|
50
|
+
methods.push(` public ${method.returnType} ${method.methodName}( ${method.methodParams})
|
|
51
|
+
{
|
|
52
|
+
${methodCode}
|
|
53
|
+
}`);
|
|
54
|
+
}
|
|
55
|
+
return `${GeneratedFileHeaderWithNullable}
|
|
56
|
+
|
|
57
|
+
using System;
|
|
58
|
+
using System.Net;
|
|
59
|
+
using System.Text.Json;
|
|
60
|
+
using System.Text.Json.Serialization;
|
|
61
|
+
using System.Threading.Tasks;
|
|
62
|
+
using Microsoft.AspNetCore.Mvc;${mock.methods.some((m) => m.methodParams.includes("MultipartReader")) ? `\nusing Microsoft.AspNetCore.WebUtilities;` : ""}
|
|
63
|
+
using ${mock.namespace}.Models;
|
|
64
|
+
using TypeSpec.Helpers;
|
|
65
|
+
|
|
66
|
+
namespace ${mock.namespace}
|
|
67
|
+
{
|
|
68
|
+
/// <summary>
|
|
69
|
+
/// This is a mock implementation of the business logic interface for
|
|
70
|
+
/// demonstration and early development. Feel free to overwrite this file.
|
|
71
|
+
/// Or replace it with another implementation, and register that implementation
|
|
72
|
+
/// in the dependency injection container
|
|
73
|
+
/// </summary>
|
|
74
|
+
/// <param name="initializer">The initializer class, registered with dependency injection</param>
|
|
75
|
+
public class ${mock.className} : ${mock.interfaceName}
|
|
76
|
+
{
|
|
77
|
+
public ${mock.className}(IInitializer initializer)
|
|
78
|
+
{
|
|
79
|
+
_initializer = initializer;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private IInitializer _initializer;
|
|
83
|
+
|
|
84
|
+
${methods.join("\n\n")}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
function getMockRegistration(mocks) {
|
|
90
|
+
if (mocks.length < 1)
|
|
91
|
+
return "";
|
|
92
|
+
const cache = new Map();
|
|
93
|
+
return `${GeneratedFileHeaderWithNullable}
|
|
94
|
+
|
|
95
|
+
using Microsoft.AspNetCore.Http.Features;
|
|
96
|
+
${mocks
|
|
97
|
+
.flatMap((m) => m.usings)
|
|
98
|
+
.filter((t) => {
|
|
99
|
+
const result = !cache.has(t);
|
|
100
|
+
cache.set(t, t);
|
|
101
|
+
return result;
|
|
102
|
+
})
|
|
103
|
+
.flatMap((e) => `using ${e};`)
|
|
104
|
+
.join("\n")}
|
|
105
|
+
using ${mocks[0].namespace};
|
|
106
|
+
|
|
107
|
+
namespace TypeSpec.Helpers
|
|
108
|
+
{
|
|
109
|
+
/// <summary>
|
|
110
|
+
/// Register Business Logic implementations. Replace with actual implementations when available.
|
|
111
|
+
/// </summary>
|
|
112
|
+
public static class MockRegistration
|
|
113
|
+
{
|
|
114
|
+
public static void Register(WebApplicationBuilder builder)
|
|
115
|
+
{
|
|
116
|
+
builder.Services.AddScoped<IJsonSerializationProvider, JsonSerializationProvider>();
|
|
117
|
+
// Used for mock implementation only. Remove once business logic interfaces are implemented.
|
|
118
|
+
builder.Services.AddSingleton<IDictionary<Type, object?>>(new Dictionary<Type, object?>());
|
|
119
|
+
builder.Services.AddScoped<IInitializer, Initializer>();
|
|
120
|
+
// Mock business logic implementations
|
|
121
|
+
${mocks.flatMap((m) => ` builder.Services.AddScoped<${m.interfaceName}, ${m.className}>();`).join("\n")}
|
|
122
|
+
// Included for multipart/form-data support
|
|
123
|
+
builder.Services.Configure<FormOptions>(options =>
|
|
124
|
+
{
|
|
125
|
+
options.MemoryBufferThreshold = int.MaxValue;
|
|
126
|
+
options.MultipartBodyLengthLimit = int.MaxValue;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}`;
|
|
131
|
+
}
|
|
132
|
+
function getProjectStartup(useSwagger, openApiPath) {
|
|
133
|
+
return `${GeneratedFileHeaderWithNullable}
|
|
134
|
+
|
|
135
|
+
using TypeSpec.Helpers;
|
|
136
|
+
|
|
137
|
+
var builder = WebApplication.CreateBuilder(args);
|
|
138
|
+
|
|
139
|
+
// Add services to the container.
|
|
140
|
+
builder.Services.AddControllersWithViews();
|
|
141
|
+
builder.Services.AddEndpointsApiExplorer();
|
|
142
|
+
${useSwagger ? "builder.Services.AddSwaggerGen();" : ""}
|
|
143
|
+
MockRegistration.Register(builder);
|
|
144
|
+
|
|
145
|
+
var app = builder.Build();
|
|
146
|
+
|
|
147
|
+
// Configure the HTTP request pipeline.
|
|
148
|
+
if (!app.Environment.IsDevelopment())
|
|
149
|
+
{
|
|
150
|
+
app.UseExceptionHandler("/Home/Error");
|
|
151
|
+
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
152
|
+
app.UseHsts();
|
|
153
|
+
}
|
|
154
|
+
${useSwagger
|
|
155
|
+
? code `else
|
|
156
|
+
{
|
|
157
|
+
app.UseSwagger();
|
|
158
|
+
app.UseSwaggerUI( c=> {
|
|
159
|
+
c.DocumentTitle = "TypeSpec Generated OpenAPI Viewer";
|
|
160
|
+
c.SwaggerEndpoint("/openapi.yaml", "TypeSpec Generated OpenAPI Docs");
|
|
161
|
+
c.RoutePrefix = "swagger";
|
|
162
|
+
});
|
|
163
|
+
}\n`
|
|
164
|
+
: ""}
|
|
165
|
+
|
|
166
|
+
app.UseHttpsRedirection();
|
|
167
|
+
app.UseStaticFiles();
|
|
168
|
+
app.Use(async (context, next) =>
|
|
169
|
+
{
|
|
170
|
+
context.Request.EnableBuffering();
|
|
171
|
+
await next();
|
|
172
|
+
});
|
|
173
|
+
${useSwagger
|
|
174
|
+
? code `
|
|
175
|
+
app.MapGet("/openapi.yaml", async (HttpContext context) =>
|
|
176
|
+
{
|
|
177
|
+
var externalFilePath = "${openApiPath}"; // Full path to the file outside the project
|
|
178
|
+
if (!File.Exists(externalFilePath))
|
|
179
|
+
{
|
|
180
|
+
context.Response.StatusCode = StatusCodes.Status404NotFound;
|
|
181
|
+
await context.Response.WriteAsync("OpenAPI spec not found.");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
context.Response.ContentType = "application/json";
|
|
185
|
+
await context.Response.SendFileAsync(externalFilePath);
|
|
186
|
+
});\n`
|
|
187
|
+
: ""}
|
|
188
|
+
|
|
189
|
+
app.UseRouting();
|
|
190
|
+
|
|
191
|
+
app.UseAuthorization();
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
app.MapControllerRoute(
|
|
195
|
+
name: "default",
|
|
196
|
+
pattern: "{controller=Home}/{action=Index}/{id?}");
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
app.Run();`;
|
|
200
|
+
}
|
|
201
|
+
function getInitializerInterface() {
|
|
202
|
+
return `${GeneratedFileHeaderWithNullable}
|
|
203
|
+
|
|
204
|
+
namespace TypeSpec.Helpers
|
|
205
|
+
{
|
|
206
|
+
/// <summary>
|
|
207
|
+
/// Interface for object initialization in mocks
|
|
208
|
+
/// </summary>
|
|
209
|
+
public interface IInitializer
|
|
210
|
+
{
|
|
211
|
+
/// <summary>
|
|
212
|
+
/// Initialize an object fo the given type
|
|
213
|
+
/// </summary>
|
|
214
|
+
/// <param name="type"> The type to initialize</param>
|
|
215
|
+
/// <returns>An instance of the given type. Or null if initialization was impossible.</returns>
|
|
216
|
+
object? Initialize(System.Type type);
|
|
217
|
+
|
|
218
|
+
/// <summary>
|
|
219
|
+
/// Initialize an object of the given type
|
|
220
|
+
/// </summary>
|
|
221
|
+
/// <typeparam name="T">The type to initialize</typeparam>
|
|
222
|
+
/// <returns>An instance of the given type</returns>
|
|
223
|
+
T Initialize<T>() where T : class, new();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
`;
|
|
227
|
+
}
|
|
228
|
+
function getInitializerImplementation() {
|
|
229
|
+
return `${GeneratedFileHeaderWithNullable}
|
|
230
|
+
|
|
231
|
+
namespace TypeSpec.Helpers
|
|
232
|
+
{
|
|
233
|
+
/// <summary>
|
|
234
|
+
/// Default initializer for mock implementations of business logic interfaces
|
|
235
|
+
/// </summary>
|
|
236
|
+
public class Initializer : IInitializer
|
|
237
|
+
{
|
|
238
|
+
/// <summary>
|
|
239
|
+
/// Instantiate the initializer. The cache *should* be instantiated using ASP.Net Core's dependency injection
|
|
240
|
+
/// </summary>
|
|
241
|
+
/// <param name="cache"></param>
|
|
242
|
+
public Initializer(IDictionary<Type, object?> cache)
|
|
243
|
+
{
|
|
244
|
+
Cache = cache;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
internal virtual IDictionary<Type, object?> Cache { get; }
|
|
248
|
+
|
|
249
|
+
internal object? CacheAndReturn(Type type, object? instance)
|
|
250
|
+
{
|
|
251
|
+
Cache[type] = instance;
|
|
252
|
+
return instance;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// <summary>
|
|
256
|
+
/// Initialize an object fo the given type
|
|
257
|
+
/// </summary>
|
|
258
|
+
/// <param name="type"> The type to initialize</param>
|
|
259
|
+
/// <returns>An instance of the given type. Or null if initialization was impossible.</returns>
|
|
260
|
+
public object? Initialize (Type type)
|
|
261
|
+
{
|
|
262
|
+
if (Cache.ContainsKey(type))
|
|
263
|
+
{
|
|
264
|
+
return Cache[type];
|
|
265
|
+
}
|
|
266
|
+
if (type == typeof(string))
|
|
267
|
+
{
|
|
268
|
+
return CacheAndReturn(type, string.Empty);
|
|
269
|
+
}
|
|
270
|
+
if (type == typeof(int))
|
|
271
|
+
{
|
|
272
|
+
return CacheAndReturn(type, 0);
|
|
273
|
+
}
|
|
274
|
+
if (type == typeof(long))
|
|
275
|
+
{
|
|
276
|
+
return CacheAndReturn(type, 0L);
|
|
277
|
+
}
|
|
278
|
+
if (type == typeof(float))
|
|
279
|
+
{
|
|
280
|
+
return CacheAndReturn(type, 0.0f);
|
|
281
|
+
}
|
|
282
|
+
if (type == typeof(double))
|
|
283
|
+
{
|
|
284
|
+
return CacheAndReturn(type, 0.0);
|
|
285
|
+
}
|
|
286
|
+
if (type == typeof(decimal))
|
|
287
|
+
{
|
|
288
|
+
return CacheAndReturn(type, 0.0m);
|
|
289
|
+
}
|
|
290
|
+
if (type == typeof(bool))
|
|
291
|
+
{
|
|
292
|
+
return CacheAndReturn(type, false);
|
|
293
|
+
}
|
|
294
|
+
if (type == typeof(byte))
|
|
295
|
+
{
|
|
296
|
+
return CacheAndReturn(type, (byte)0);
|
|
297
|
+
}
|
|
298
|
+
if (type == typeof(char))
|
|
299
|
+
{
|
|
300
|
+
return CacheAndReturn(type, (char)0);
|
|
301
|
+
}
|
|
302
|
+
if (type == typeof(short))
|
|
303
|
+
{
|
|
304
|
+
return CacheAndReturn(type, (short)0);
|
|
305
|
+
}
|
|
306
|
+
if (type == typeof(uint))
|
|
307
|
+
{
|
|
308
|
+
return CacheAndReturn(type, (uint)0);
|
|
309
|
+
}
|
|
310
|
+
if (type == typeof(ulong))
|
|
311
|
+
{
|
|
312
|
+
return CacheAndReturn(type, (ulong)0);
|
|
313
|
+
}
|
|
314
|
+
if (type == typeof(ushort))
|
|
315
|
+
{
|
|
316
|
+
return CacheAndReturn(type, (ushort)0);
|
|
317
|
+
}
|
|
318
|
+
if (type == typeof(sbyte))
|
|
319
|
+
{
|
|
320
|
+
return CacheAndReturn(type, (sbyte)0);
|
|
321
|
+
}
|
|
322
|
+
if (type == typeof(DateTime))
|
|
323
|
+
{
|
|
324
|
+
return CacheAndReturn(type, DateTime.UtcNow);
|
|
325
|
+
}
|
|
326
|
+
if (type == typeof(DateTimeOffset))
|
|
327
|
+
{
|
|
328
|
+
return CacheAndReturn(type, DateTimeOffset.UtcNow);
|
|
329
|
+
}
|
|
330
|
+
if ( type == typeof(TimeSpan))
|
|
331
|
+
{
|
|
332
|
+
return CacheAndReturn(type, TimeSpan.Zero);
|
|
333
|
+
}
|
|
334
|
+
if (type.IsArray)
|
|
335
|
+
{
|
|
336
|
+
var element = type.GetElementType();
|
|
337
|
+
if (element == null) return null;
|
|
338
|
+
return CacheAndReturn(type, Array.CreateInstance(element, 0));
|
|
339
|
+
}
|
|
340
|
+
if (type.IsClass)
|
|
341
|
+
{
|
|
342
|
+
return InitializeClass(type);
|
|
343
|
+
}
|
|
344
|
+
var genericType = Nullable.GetUnderlyingType(type);
|
|
345
|
+
if ( (genericType != null))
|
|
346
|
+
{
|
|
347
|
+
return Initialize(genericType);
|
|
348
|
+
}
|
|
349
|
+
return new object();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/// <summary>
|
|
353
|
+
/// Initialize an object of the given type
|
|
354
|
+
/// </summary>
|
|
355
|
+
/// <typeparam name="T">The type to initialize</typeparam>
|
|
356
|
+
/// <returns>An instance of the given type</returns>
|
|
357
|
+
public T Initialize<T>() where T: class, new()
|
|
358
|
+
{
|
|
359
|
+
var result = new T();
|
|
360
|
+
var initialized = InitializeClass(typeof(T), result);
|
|
361
|
+
return initialized as T ?? result;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private object? InitializeClass(Type type, object? instance = null)
|
|
365
|
+
{
|
|
366
|
+
if (Cache.ContainsKey(type))
|
|
367
|
+
{
|
|
368
|
+
instance = Cache[type];
|
|
369
|
+
return instance;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
var result = instance == null ? Activator.CreateInstance(type) : instance;
|
|
373
|
+
foreach (var property in type.GetProperties())
|
|
374
|
+
{
|
|
375
|
+
if (property.CanWrite)
|
|
376
|
+
{
|
|
377
|
+
var propertyType = property.PropertyType;
|
|
378
|
+
property.SetValue(result, Initialize(propertyType));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return CacheAndReturn(type, result);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
`;
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=scaffolding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffolding.js","sourceRoot":"","sources":["../../../src/lib/scaffolding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,IAAI,EAAE,MAAM,sCAAsC,CAAC;AAC1E,OAAO,EAAE,+BAA+B,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAoBpD,MAAM,UAAU,qBAAqB,CACnC,OAAoD,EACpD,UAAmB,EACnB,WAAmB;IAEnB,MAAM,WAAW,GAAwB,EAAE,CAAC;IAC5C,WAAW,CAAC,IAAI,CACd,IAAI,iBAAiB,CAAC;QACpB,QAAQ,EAAE,iBAAiB;QAC3B,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,uBAAuB;KACrC,CAAC,EACF,IAAI,iBAAiB,CAAC;QACpB,QAAQ,EAAE,gBAAgB;QAC1B,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,4BAA4B;KAC1C,CAAC,EACF,IAAI,iBAAiB,CAAC;QACpB,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,OAAO;QAChB,WAAW,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,UAAU,EAAE,WAAW,CAAC;QAC7D,IAAI,EAAE,KAAK;KACZ,CAAC,CACH,CAAC;IACF,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,OAAoD,EACpD,aAAyC;IAEzC,MAAM,WAAW,GAAwB,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAkC,EAAE,CAAC;IAChD,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,aAAa,EAAE,CAAC;QACzC,WAAW,CAAC,IAAI,CACd,IAAI,iBAAiB,CAAC;YACpB,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,KAAK;YAChC,OAAO,EAAE,OAAO;YAChB,WAAW,EAAE,GAAG,EAAE,CAAC,8BAA8B,CAAC,IAAI,EAAE,IAAI,CAAC;YAC7D,IAAI,EAAE,WAAW;SAClB,CAAC,CACH,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,WAAW,CAAC,IAAI,CACd,IAAI,iBAAiB,CAAC;YACpB,QAAQ,EAAE,qBAAqB;YAC/B,OAAO,EAAE,OAAO;YAChB,WAAW,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC;YAC7C,IAAI,EAAE,WAAW;SAClB,CAAC,CACH,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,8BAA8B,CAAC,KAAa,EAAE,IAAiC;IACtF,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,MAAM,UAAU,GACd,MAAM,CAAC,sBAAsB,KAAK,SAAS;YACzC,CAAC,CAAC,kDAAkD,MAAM,CAAC,sBAAsB,OAAO;YACxF,CAAC,CAAC,4BAA4B,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM,CAAC,YAAY;;cAEnF,UAAU;UACd,CAAC,CAAC;IACV,CAAC;IACD,OAAO,GAAG,+BAA+B;;;;;;;iCAOV,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC,4CAA4C,CAAC,CAAC,CAAC,EAAE;QACjJ,IAAI,CAAC,SAAS;;;YAGV,IAAI,CAAC,SAAS;;;;;;;;;mBASP,IAAI,CAAC,SAAS,MAAM,IAAI,CAAC,aAAa;;iBAExC,IAAI,CAAC,SAAS;;;;;;;EAO7B,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;;;GAGnB,CAAC;AACJ,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAoC;IAC/D,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,KAAK,GAAwB,IAAI,GAAG,EAAkB,CAAC;IAC7D,OAAO,GAAG,+BAA+B;;;EAGzC,KAAK;SACJ,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;SACxB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACZ,MAAM,MAAM,GAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACtC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAChB,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;SACD,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC;SAC7B,IAAI,CAAC,IAAI,CAAC;QACL,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;;;;;;;;;;;;;;;;EAgBxB,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,0CAA0C,CAAC,CAAC,aAAa,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;;;EAShH,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,UAAmB,EAAE,WAAmB;IACjE,OAAO,GAAG,+BAA+B;;;;;;;;;EASzC,UAAU,CAAC,CAAC,CAAC,mCAAmC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;EAarD,UAAU;QACR,CAAC,CAAC,IAAI,CAAA;;;;;;;;IAQN;QACA,CAAC,CAAC,EACN;;;;;;;;;EAUE,UAAU;QACR,CAAC,CAAC,IAAI,CAAA;;;8BAGoB,WAAW;;;;;;;;;MASnC;QACF,CAAC,CAAC,EACN;;;;;;;;;;;;WAYW,CAAC;AACZ,CAAC;AAED,SAAS,uBAAuB;IAC9B,OAAO,GAAG,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;CAwB1C,CAAC;AACF,CAAC;AAED,SAAS,4BAA4B;IACnC,OAAO,GAAG,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6J1C,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/lib/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,WAAW,EA0BZ,MAAM,oBAAoB,CAAC;AA+C5B,OAAO,EAAE,2BAA2B,EAAoB,MAAM,UAAU,CAAC;AA2BzE,wBAAsB,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,2BAA2B,CAAC,iBAgjD9E"}
|
|
@@ -4,9 +4,10 @@ import { createRekeyableMap } from "@typespec/compiler/utils";
|
|
|
4
4
|
import { Visibility, createMetadataInfo, getHeaderFieldName, getHttpOperation, getHttpPart, isHeader, isPathParam, isQueryParam, isStatusCode, } from "@typespec/http";
|
|
5
5
|
import { getResourceOperation } from "@typespec/rest";
|
|
6
6
|
import { execFile } from "child_process";
|
|
7
|
-
import { getSerializationSourceFiles } from "./boilerplate.js";
|
|
7
|
+
import { GeneratedFileHeader, GeneratedFileHeaderWithNullable, getSerializationSourceFiles, } from "./boilerplate.js";
|
|
8
8
|
import { CSharpSourceType, CSharpType, NameCasingType, } from "./interfaces.js";
|
|
9
9
|
import { reportDiagnostic } from "./lib.js";
|
|
10
|
+
import { getBusinessLogicImplementations, getScaffoldingHelpers, } from "./scaffolding.js";
|
|
10
11
|
import { getRecordType, isKnownReferenceType } from "./type-helpers.js";
|
|
11
12
|
import { HttpMetadata, UnknownType, coalesceTypes, ensureCSharpIdentifier, ensureCleanDirectory, formatComment, getCSharpIdentifier, getCSharpStatusCode, getCSharpType, getCSharpTypeForIntrinsic, getCSharpTypeForScalar, getModelAttributes, getModelInstantiationName, getOperationVerbDecorator, isValueType, } from "./utils.js";
|
|
12
13
|
export async function $onEmit(context) {
|
|
@@ -16,12 +17,20 @@ export async function $onEmit(context) {
|
|
|
16
17
|
const doNotEmit = context.program.compilerOptions.noEmit || false;
|
|
17
18
|
class CSharpCodeEmitter extends CodeTypeEmitter {
|
|
18
19
|
#metadateMap = new Map();
|
|
19
|
-
#
|
|
20
|
-
|
|
20
|
+
#generatedFileHeaderWithNullable = GeneratedFileHeaderWithNullable;
|
|
21
|
+
#generatedFileHeader = GeneratedFileHeader;
|
|
21
22
|
#sourceTypeKey = "sourceType";
|
|
22
23
|
#libraryFiles = getSerializationSourceFiles(this.emitter);
|
|
23
24
|
#baseNamespace = undefined;
|
|
24
25
|
#emitterOutputType = context.options["output-type"];
|
|
26
|
+
#emitMocks = context.options["emit-mocks"];
|
|
27
|
+
#useSwagger = context.options["use-swaggerui"] || false;
|
|
28
|
+
#openapiPath = context.options["openapi-path"] || "openapi/openapi.yaml";
|
|
29
|
+
#mockRegistrations = new Map();
|
|
30
|
+
#mockHelpers = this.#emitMocks === "all"
|
|
31
|
+
? getScaffoldingHelpers(this.emitter, this.#useSwagger, this.#openapiPath)
|
|
32
|
+
: [];
|
|
33
|
+
#mockFiles = [];
|
|
25
34
|
#metaInfo = createMetadataInfo(this.emitter.getProgram(), {
|
|
26
35
|
canonicalVisibility: Visibility.Read,
|
|
27
36
|
canShareProperty: (p) => true,
|
|
@@ -71,8 +80,7 @@ export async function $onEmit(context) {
|
|
|
71
80
|
const doc = getDoc(this.emitter.getProgram(), en);
|
|
72
81
|
const attributes = getModelAttributes(program, en, enumName);
|
|
73
82
|
this.#metadateMap.set(en, new CSharpType({ name: enumName, namespace: namespace }));
|
|
74
|
-
return this.emitter.result.declaration(enumName, code `${this.#
|
|
75
|
-
// <auto-generated />
|
|
83
|
+
return this.emitter.result.declaration(enumName, code `${this.#generatedFileHeader}
|
|
76
84
|
|
|
77
85
|
${this.#emitUsings()}
|
|
78
86
|
|
|
@@ -150,8 +158,7 @@ export async function $onEmit(context) {
|
|
|
150
158
|
const doc = getDoc(this.emitter.getProgram(), model);
|
|
151
159
|
const attributes = getModelAttributes(this.emitter.getProgram(), model, className);
|
|
152
160
|
this.#metadateMap.set(model, new CSharpType({ name: className, namespace: namespace }));
|
|
153
|
-
const decl = this.emitter.result.declaration(className, code `${this.#
|
|
154
|
-
// <auto-generated />
|
|
161
|
+
const decl = this.emitter.result.declaration(className, code `${this.#generatedFileHeader}
|
|
155
162
|
|
|
156
163
|
${this.#emitUsings()}
|
|
157
164
|
|
|
@@ -416,8 +423,7 @@ export async function $onEmit(context) {
|
|
|
416
423
|
const doc = getDoc(this.emitter.getProgram(), iface);
|
|
417
424
|
const attributes = getModelAttributes(this.emitter.getProgram(), iface, ifaceName);
|
|
418
425
|
this.#metadateMap.set(iface, new CSharpType({ name: ifaceName, namespace: namespace }));
|
|
419
|
-
const decl = this.emitter.result.declaration(ifaceName, code `${this.#
|
|
420
|
-
// <auto-generated />
|
|
426
|
+
const decl = this.emitter.result.declaration(ifaceName, code `${this.#generatedFileHeaderWithNullable}
|
|
421
427
|
|
|
422
428
|
${this.#emitUsings()}
|
|
423
429
|
|
|
@@ -458,6 +464,16 @@ export async function $onEmit(context) {
|
|
|
458
464
|
const builder = new StringBuilder();
|
|
459
465
|
const metadata = new HttpMetadata();
|
|
460
466
|
const context = this.emitter.getContext();
|
|
467
|
+
const name = `${ensureCSharpIdentifier(this.emitter.getProgram(), iface, iface.name, NameCasingType.Class)}`;
|
|
468
|
+
const ifaceNamespace = this.#getOrSetBaseNamespace(iface);
|
|
469
|
+
const namespace = `${ifaceNamespace}`;
|
|
470
|
+
const mock = {
|
|
471
|
+
className: name,
|
|
472
|
+
interfaceName: `I${name}`,
|
|
473
|
+
methods: [],
|
|
474
|
+
namespace: namespace,
|
|
475
|
+
usings: [`${ifaceNamespace}.Models`],
|
|
476
|
+
};
|
|
461
477
|
for (const [name, operation] of iface.operations) {
|
|
462
478
|
const doc = getDoc(this.emitter.getProgram(), operation);
|
|
463
479
|
const returnTypes = [];
|
|
@@ -469,15 +485,34 @@ export async function $onEmit(context) {
|
|
|
469
485
|
const returnType = returnInfo?.type || UnknownType;
|
|
470
486
|
const opName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
|
|
471
487
|
let opDecl;
|
|
488
|
+
let opImpl;
|
|
472
489
|
if (this.#isMultipartRequest(httpOp)) {
|
|
473
|
-
|
|
490
|
+
opImpl = {
|
|
491
|
+
methodName: `${opName}Async`,
|
|
492
|
+
methodParams: `${this.#emitInterfaceOperationParameters(operation, "MultipartReader reader")}`,
|
|
493
|
+
returnType: `${returnType.name === "void" ? "Task" : `Task<${returnType.getTypeReference(context.scope)}>`}`,
|
|
494
|
+
instantiatedReturnType: returnType.name === "void"
|
|
495
|
+
? undefined
|
|
496
|
+
: `${returnType.getTypeReference(context.scope)}`,
|
|
497
|
+
};
|
|
498
|
+
opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${opImpl.returnType} ${opImpl.methodName}( ${opImpl.methodParams});`);
|
|
474
499
|
}
|
|
475
500
|
else {
|
|
476
|
-
|
|
501
|
+
opImpl = {
|
|
502
|
+
methodName: `${opName}Async`,
|
|
503
|
+
methodParams: `${this.#emitInterfaceOperationParameters(operation)}`,
|
|
504
|
+
returnType: `${returnType.name === "void" ? "Task" : `Task<${returnType.getTypeReference(context.scope)}>`}`,
|
|
505
|
+
instantiatedReturnType: returnType.name === "void"
|
|
506
|
+
? undefined
|
|
507
|
+
: `${returnType.getTypeReference(context.scope)}`,
|
|
508
|
+
};
|
|
509
|
+
opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${opImpl.returnType} ${opImpl.methodName}( ${opImpl.methodParams});`);
|
|
477
510
|
}
|
|
511
|
+
mock.methods.push(opImpl);
|
|
478
512
|
builder.push(code `${opDecl.value}\n`);
|
|
479
513
|
this.emitter.emitInterfaceOperation(operation);
|
|
480
514
|
}
|
|
515
|
+
this.#mockRegistrations.set(mock.interfaceName, mock);
|
|
481
516
|
return builder.reduce();
|
|
482
517
|
}
|
|
483
518
|
interfaceOperationDeclarationContext(operation) {
|
|
@@ -717,8 +752,7 @@ export async function $onEmit(context) {
|
|
|
717
752
|
const doc = getDoc(this.emitter.getProgram(), union);
|
|
718
753
|
const attributes = getModelAttributes(program, union, unionName);
|
|
719
754
|
this.#metadateMap.set(union, new CSharpType({ name: unionName, namespace: namespace }));
|
|
720
|
-
return this.emitter.result.declaration(unionName, code `${this.#
|
|
721
|
-
// <auto-generated />
|
|
755
|
+
return this.emitter.result.declaration(unionName, code `${this.#generatedFileHeader}
|
|
722
756
|
|
|
723
757
|
${this.#emitUsings()}
|
|
724
758
|
|
|
@@ -878,7 +912,7 @@ export async function $onEmit(context) {
|
|
|
878
912
|
let context = controllers.get(name);
|
|
879
913
|
if (context !== undefined)
|
|
880
914
|
return context;
|
|
881
|
-
const sourceFile = this.emitter.createSourceFile(`controllers/${name}
|
|
915
|
+
const sourceFile = this.emitter.createSourceFile(`controllers/${name}Controller.cs`);
|
|
882
916
|
const namespace = this.#getOrSetBaseNamespace(operation);
|
|
883
917
|
const modelNamespace = `${namespace}.Models`;
|
|
884
918
|
sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Controller;
|
|
@@ -928,6 +962,18 @@ export async function $onEmit(context) {
|
|
|
928
962
|
if (sourceFile === libFile.source)
|
|
929
963
|
return libFile.emitted;
|
|
930
964
|
}
|
|
965
|
+
if (this.#emitMocks === "all") {
|
|
966
|
+
for (const helper of this.#mockHelpers) {
|
|
967
|
+
if (sourceFile === helper.source)
|
|
968
|
+
return helper.emitted;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
if (this.#mockFiles.length > 0) {
|
|
972
|
+
for (const mock of this.#mockFiles) {
|
|
973
|
+
if (sourceFile === mock.source)
|
|
974
|
+
return mock.emitted;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
931
977
|
const emittedSourceFile = {
|
|
932
978
|
path: sourceFile.path,
|
|
933
979
|
contents: "",
|
|
@@ -952,17 +998,21 @@ export async function $onEmit(context) {
|
|
|
952
998
|
#emitControllerContents(file) {
|
|
953
999
|
const namespace = file.meta.namespace;
|
|
954
1000
|
const contents = new StringBuilder();
|
|
955
|
-
contents.push(`${this.#
|
|
956
|
-
contents.push("// <auto-generated />\n\n");
|
|
1001
|
+
contents.push(`${this.#generatedFileHeader}\n\n`);
|
|
957
1002
|
contents.push(code `${this.#emitUsings(file)}\n`);
|
|
958
1003
|
contents.push("\n");
|
|
959
1004
|
contents.push(`namespace ${namespace}.Controllers\n`);
|
|
960
1005
|
contents.push("{\n");
|
|
961
1006
|
contents.push("[ApiController]\n");
|
|
962
|
-
contents.push(`public
|
|
1007
|
+
contents.push(`public partial class ${file.meta["resource"]}: ControllerBase\n`);
|
|
1008
|
+
contents.push("{\n");
|
|
1009
|
+
contents.push("\n");
|
|
1010
|
+
contents.push(`public ${file.meta["resource"]}(I${file.meta.resourceName} operations)\n`);
|
|
963
1011
|
contents.push("{\n");
|
|
1012
|
+
contents.push(` ${file.meta.resourceName}Impl = operations;\n`);
|
|
1013
|
+
contents.push("}");
|
|
964
1014
|
contents.push("\n");
|
|
965
|
-
contents.push(code `internal
|
|
1015
|
+
contents.push(code `internal virtual I${file.meta.resourceName} ${file.meta.resourceName}Impl { get;}\n`);
|
|
966
1016
|
for (const decl of file.globalScope.declarations) {
|
|
967
1017
|
contents.push(decl.value + "\n");
|
|
968
1018
|
}
|
|
@@ -1089,6 +1139,16 @@ export async function $onEmit(context) {
|
|
|
1089
1139
|
for (const source of this.#libraryFiles) {
|
|
1090
1140
|
sourceFiles.push(source.source);
|
|
1091
1141
|
}
|
|
1142
|
+
if (this.#emitMocks === "all") {
|
|
1143
|
+
for (const helper of this.#mockHelpers) {
|
|
1144
|
+
sourceFiles.push(helper.source);
|
|
1145
|
+
}
|
|
1146
|
+
if (this.#mockRegistrations.size > 0) {
|
|
1147
|
+
const mocks = getBusinessLogicImplementations(this.emitter, this.#mockRegistrations);
|
|
1148
|
+
this.#mockFiles.push(...mocks);
|
|
1149
|
+
sourceFiles.push(...mocks.flatMap((l) => l.source));
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1092
1152
|
const emittedSourceFiles = [];
|
|
1093
1153
|
for (const source of sourceFiles) {
|
|
1094
1154
|
switch (this.#emitterOutputType) {
|