Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d96d6f5
generate error sets (#57)
heymb Sep 18, 2023
80e741f
Publish 2.9.0.
Sep 18, 2023
04b7493
Make failed expectations more clear in conformanceApiTests.ts.
mitchknife Dec 15, 2023
b6f838e
Clean up conformance test runner.
mitchknife Dec 15, 2023
c11db89
Add simple working fastify app for conformance tests.
mitchknife Dec 15, 2023
b4f1516
Add "hand rolled" ConformanceApi plugin.
mitchknife Dec 15, 2023
724ddca
Generate fastify plugin shell.
mitchknife Dec 15, 2023
ae6f3b9
Fill out codegen for fastify plugin.
mitchknife Dec 16, 2023
0901688
Generate separate copy of types for fastify plugin.
mitchknife Dec 16, 2023
862cc03
Consolidate duplicate code into local functions.
mitchknife Dec 16, 2023
9175554
Delete handRolledPlugin.ts
mitchknife Dec 16, 2023
a7a77ef
Clean up plain js support.
mitchknife Dec 16, 2023
dd18f5a
Restructure conformance folder structure.
mitchknife Dec 17, 2023
6470f1d
Only generate fastify plugin when option is supplied.
mitchknife Dec 17, 2023
da68074
Validate ConformanceTests.json before running tests.
mitchknife Dec 18, 2023
26b39c1
Support raw http requests in conformance tests.
mitchknife Dec 18, 2023
c576130
Support case insensitive querystring keys in fastify plugin.
mitchknife Dec 18, 2023
d7bfd59
Enable case insensitve routing for fastify conformance servers.
mitchknife Dec 18, 2023
6102789
Add service error handling support to fastify plugin.
mitchknife Dec 18, 2023
c625c24
Add clarifying doc comments.
mitchknife Dec 18, 2023
0c72936
Run conformance js and ts fastify plugins together.
mitchknife Dec 19, 2023
d17ad41
Merge pull request #59 from mitchknife/fastify
mitchknife Dec 29, 2023
35be5b5
Add JsonSchema support to fastify plugin.
mitchknife Dec 30, 2023
a00bbc0
Switch on `ServiceTypeKind`
mitchknife Jan 1, 2024
51f5c1f
Assume non-null value from `GetFieldType`.
mitchknife Jan 1, 2024
580c68b
Don't use interpolated strings to prevent analysis warning.
mitchknife Jan 1, 2024
fff1953
Merge pull request #60 from mitchknife/fastify-json-schema
mitchknife Jan 2, 2024
e36eb2d
Miscellaneous updates.
ejball Jan 20, 2024
035032d
Miscellaneous updates.
ejball Mar 22, 2024
699dfaf
Don't align raw literal string content.
ejball Mar 23, 2024
ba84bb9
Allow explicit docs publish.
ejball Mar 23, 2024
f590b40
Update dependencies.
ejball Mar 23, 2024
a82d484
Fix docs publish.
ejball Mar 26, 2024
e781147
Update dependency.
ejball May 15, 2024
4a90067
Install .NET 6.0 and 8.0 SDKs.
ejball May 17, 2024
761d526
Merge template/faithlife-build.
ejball May 17, 2024
de9699f
Add file name suffix option (#61)
victorbush May 17, 2024
adefd41
Publish 2.10.0 (#62)
victorbush May 20, 2024
2b22cfa
Documentation updated.
FacilityApiBot May 20, 2024
e1a282a
Fix types file references when using file-name-suffix option . (#63)
victorbush May 21, 2024
6a63fb0
Publish 2.10.1. (#64)
victorbush May 21, 2024
4928a03
Enable use of external enums as URI and header params. (#65)
victorbush Jun 26, 2024
9a0260e
Publish 2.11.0. (#66)
victorbush Jun 26, 2024
0df8f2e
Ignore events for now.
ejball Jul 4, 2024
86206e7
Publish 2.11.1.
ejball Jul 4, 2024
6365737
Merge pull request #67 from ejball/events
ejball Jul 4, 2024
1824ab0
Documentation updated.
FacilityApiBot Jul 4, 2024
6cf4974
Add tests for current TS output of DTOs with "optional" and required …
heymb Aug 5, 2024
a085f10
Support generating required fields as not optional in TS
Aug 2, 2024
ff4f6ea
Run codegen since required now is not optional
Aug 2, 2024
499aa47
Update version number and release notes per contributing docs since g…
Aug 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Only generate fastify plugin when option is supplied.
- This allows the plugin to live wherever the user wants it to live.
- Ideally I would think the express server should be generated the same way
but that would be a breaking change, so maybe not needed?
  • Loading branch information
mitchknife committed Dec 17, 2023
commit 6470f1db36cda44256825c71e2fb197c65775cd7
323 changes: 166 additions & 157 deletions src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ public static int GenerateJavaScript(JavaScriptGeneratorSettings settings) =>
/// </summary>
public override CodeGenOutput GenerateOutput(ServiceInfo service)
{
if (Fastify)
return GenerateFastifyPluginOutput(service);

var httpServiceInfo = HttpServiceInfo.Create(service);

var moduleName = ModuleName ?? service.Name;
var camelCaseModuleName = CodeGenUtility.ToCamelCase(moduleName);
var capModuleName = CodeGenUtility.Capitalize(moduleName);
var typesFileName = CodeGenUtility.Uncapitalize(moduleName) + "Types" + (TypeScript ? ".ts" : ".js");
var clientFileName = CodeGenUtility.Uncapitalize(moduleName) + (TypeScript ? ".ts" : ".js");
Expand Down Expand Up @@ -510,212 +512,219 @@ public override CodeGenOutput GenerateOutput(ServiceInfo service)
}));
}

if (Fastify)
return new CodeGenOutput(namedTexts, new List<CodeGenPattern>());
}

/// <summary>
/// Applies generator-specific settings.
/// </summary>
public override void ApplySettings(FileGeneratorSettings settings)
{
var ourSettings = (JavaScriptGeneratorSettings) settings;
ModuleName = ourSettings.ModuleName;
TypeScript = ourSettings.TypeScript;
Express = ourSettings.Express;
Fastify = ourSettings.Fastify;
DisableESLint = ourSettings.DisableESLint;
}

/// <summary>
/// Supports writing output to a single file.
/// </summary>
public override bool SupportsSingleOutput => true;

private CodeGenOutput GenerateFastifyPluginOutput(ServiceInfo service)
{
var httpServiceInfo = HttpServiceInfo.Create(service);
var moduleName = ModuleName ?? service.Name;
var capModuleName = CodeGenUtility.Capitalize(moduleName);
var camelCaseModuleName = CodeGenUtility.ToCamelCase(moduleName);
var pluginFileName = CodeGenUtility.Uncapitalize(moduleName) + "Plugin" + (TypeScript ? ".ts" : ".js");

var file = CreateFile(pluginFileName, code =>
{
var pluginFileName = CodeGenUtility.Uncapitalize(moduleName) + "Plugin" + (TypeScript ? ".ts" : ".js");
namedTexts.Add(CreateFile("fastify/" + pluginFileName, code =>
{
WriteFileHeader(code);
WriteFileHeader(code);

if (!TypeScript)
code.WriteLine("'use strict';");
if (!TypeScript)
code.WriteLine("'use strict';");

code.WriteLine();
code.WriteLine();

var fastifyImports = new List<string>();
if (TypeScript)
fastifyImports.Add("FastifyPluginAsync");
WriteImports(code, fastifyImports, "fastify");
var fastifyImports = new List<string>();
if (TypeScript)
fastifyImports.Add("FastifyPluginAsync");
WriteImports(code, fastifyImports, "fastify");

var facilityImports = new List<string>();
if (TypeScript)
{
facilityImports.Add("IServiceResult");
facilityImports.Add("IServiceError");
}
WriteImports(code, facilityImports, "facility-core");
var facilityImports = new List<string>();
if (TypeScript)
{
facilityImports.Add("IServiceResult");
facilityImports.Add("IServiceError");
}
WriteImports(code, facilityImports, "facility-core");

code.WriteLine();
WriteStandardErrorCodesVariable("standardErrorCodes", code, httpServiceInfo.ErrorSets);
code.WriteLine();
WriteStandardErrorCodesVariable("standardErrorCodes", code, httpServiceInfo.ErrorSets);

code.WriteLine();
WriteParseBooleanFunction("parseBoolean", code);
code.WriteLine();
WriteParseBooleanFunction("parseBoolean", code);

if (TypeScript)
if (TypeScript)
{
code.WriteLine();
using (code.Block($"export type {capModuleName}PluginOptions = {{", "}"))
{
code.WriteLine();
using (code.Block($"export type {capModuleName}PluginOptions = {{", "}"))
{
code.WriteLine($"api: I{capModuleName};");
}
code.WriteLine($"api: I{capModuleName};");
}
}

code.WriteLine();
using (code.Block($"export const {camelCaseModuleName}Plugin" + IfTypeScript($": FastifyPluginAsync<{capModuleName}PluginOptions>") + " = async (fastify, opts) => {", "}"))
code.WriteLine();
using (code.Block($"export const {camelCaseModuleName}Plugin" + IfTypeScript($": FastifyPluginAsync<{capModuleName}PluginOptions>") + " = async (fastify, opts) => {", "}"))
{
code.WriteLine("const { api } = opts;");

foreach (var httpMethodInfo in httpServiceInfo.Methods)
{
code.WriteLine("const { api } = opts;");
var methodName = httpMethodInfo.ServiceMethod.Name;
var capMethodName = CodeGenUtility.Capitalize(methodName);
var fastifyPath = httpMethodInfo.Path;
foreach (var httpPathField in httpMethodInfo.PathFields)
fastifyPath = ReplaceOrdinal(fastifyPath, "{" + httpPathField.Name + "}", $":{httpPathField.Name}");

foreach (var httpMethodInfo in httpServiceInfo.Methods)
code.WriteLine();
using (code.Block("fastify.route({", "});"))
{
var methodName = httpMethodInfo.ServiceMethod.Name;
var capMethodName = CodeGenUtility.Capitalize(methodName);
var fastifyPath = httpMethodInfo.Path;
foreach (var httpPathField in httpMethodInfo.PathFields)
fastifyPath = ReplaceOrdinal(fastifyPath, "{" + httpPathField.Name + "}", $":{httpPathField.Name}");

code.WriteLine();
using (code.Block("fastify.route({", "});"))
code.WriteLine($"url: '{fastifyPath}',");
code.WriteLine($"method: '{httpMethodInfo.Method.ToUpperInvariant()}',");
using (code.Block("handler: async function (req, res) {", "}"))
{
code.WriteLine($"url: '{fastifyPath}',");
code.WriteLine($"method: '{httpMethodInfo.Method.ToUpperInvariant()}',");
using (code.Block("handler: async function (req, res) {", "}"))
code.WriteLine("const request" + IfTypeScript($": I{capMethodName}Request") + " = {};");
if (httpMethodInfo.PathFields.Count != 0)
{
code.WriteLine("const request" + IfTypeScript($": I{capMethodName}Request") + " = {};");
if (httpMethodInfo.PathFields.Count != 0)
code.WriteLine();
code.WriteLine($"const params = req.params{IfTypeScript(" as Record<string, string>")};");
foreach (var pathParam in httpMethodInfo.PathFields)
{
code.WriteLine();
code.WriteLine($"const params = req.params{IfTypeScript(" as Record<string, string>")};");
foreach (var pathParam in httpMethodInfo.PathFields)
{
code.WriteLine($"if (typeof params['{pathParam.Name}'] === 'string') request.{pathParam.ServiceField.Name} = {ParseFieldValue(pathParam.ServiceField, service, $"params['{pathParam.Name}']")};");
}
}

if (httpMethodInfo.QueryFields.Count != 0)
{
code.WriteLine();
code.WriteLine($"const query = req.query{IfTypeScript(" as Record<string, string>")};");
foreach (var queryParam in httpMethodInfo.QueryFields)
{
code.WriteLine($"if (typeof query['{queryParam.Name}'] === 'string') request.{queryParam.ServiceField.Name} = {ParseFieldValue(queryParam.ServiceField, service, $"query['{queryParam.Name}']")};");
}
code.WriteLine($"if (typeof params['{pathParam.Name}'] === 'string') request.{pathParam.ServiceField.Name} = {ParseFieldValue(pathParam.ServiceField, service, $"params['{pathParam.Name}']")};");
}
}

if (httpMethodInfo.RequestHeaderFields.Count != 0)
if (httpMethodInfo.QueryFields.Count != 0)
{
code.WriteLine();
code.WriteLine($"const query = req.query{IfTypeScript(" as Record<string, string>")};");
foreach (var queryParam in httpMethodInfo.QueryFields)
{
code.WriteLine();
code.WriteLine($"const headers = req.headers{IfTypeScript(" as Record<string, string>")};");
foreach (var header in httpMethodInfo.RequestHeaderFields)
{
string lowerHeaderName = header.Name.ToLowerInvariant();
code.WriteLine($"if (typeof headers['{lowerHeaderName}'] === 'string') request.{header.ServiceField.Name} = {ParseFieldValue(header.ServiceField, service, $"headers['{lowerHeaderName}']")};");
}
code.WriteLine($"if (typeof query['{queryParam.Name}'] === 'string') request.{queryParam.ServiceField.Name} = {ParseFieldValue(queryParam.ServiceField, service, $"query['{queryParam.Name}']")};");
}
}

if (httpMethodInfo.RequestBodyField != null)
{
code.WriteLine();
code.WriteLine($"request.{httpMethodInfo.RequestBodyField.ServiceField.Name} = req.body{IfTypeScript(" as never")};");
}
else if (httpMethodInfo.RequestNormalFields.Count != 0)
if (httpMethodInfo.RequestHeaderFields.Count != 0)
{
code.WriteLine();
code.WriteLine($"const headers = req.headers{IfTypeScript(" as Record<string, string>")};");
foreach (var header in httpMethodInfo.RequestHeaderFields)
{
code.WriteLine();
code.WriteLine($"const body = req.body{IfTypeScript(" as Record<string, never>")};");
foreach (var field in httpMethodInfo.RequestNormalFields)
code.WriteLine($"request.{field.ServiceField.Name} = body.{field.ServiceField.Name};");
string lowerHeaderName = header.Name.ToLowerInvariant();
code.WriteLine($"if (typeof headers['{lowerHeaderName}'] === 'string') request.{header.ServiceField.Name} = {ParseFieldValue(header.ServiceField, service, $"headers['{lowerHeaderName}']")};");
}
}

if (httpMethodInfo.RequestBodyField != null)
{
code.WriteLine();
code.WriteLine($"const result = await api.{methodName}(request);");

code.WriteLine($"request.{httpMethodInfo.RequestBodyField.ServiceField.Name} = req.body{IfTypeScript(" as never")};");
}
else if (httpMethodInfo.RequestNormalFields.Count != 0)
{
code.WriteLine();
using (code.Block("if (result.error) {", "}"))
code.WriteLine($"const body = req.body{IfTypeScript(" as Record<string, never>")};");
foreach (var field in httpMethodInfo.RequestNormalFields)
code.WriteLine($"request.{field.ServiceField.Name} = body.{field.ServiceField.Name};");
}

code.WriteLine();
code.WriteLine($"const result = await api.{methodName}(request);");

code.WriteLine();
using (code.Block("if (result.error) {", "}"))
{
code.WriteLine("const status = result.error.code && standardErrorCodes[result.error.code];");
code.WriteLine("res.status(status || 500).send(result.error);");
code.WriteLine("return;");
}

code.WriteLine();
using (code.Block("if (result.value) {", "}"))
{
if (httpMethodInfo.ResponseHeaderFields.Count != 0)
{
code.WriteLine("const status = result.error.code && standardErrorCodes[result.error.code];");
code.WriteLine("res.status(status || 500).send(result.error);");
code.WriteLine("return;");
code.WriteLineSkipOnce();
foreach (var field in httpMethodInfo.ResponseHeaderFields)
code.WriteLine($"if (result.value.{field.ServiceField.Name} != null) res.header('{field.Name}', result.value.{field.ServiceField.Name});");
}

code.WriteLine();
using (code.Block("if (result.value) {", "}"))
var handledResponses = httpMethodInfo.ValidResponses.ToList();
foreach (var validResponse in httpMethodInfo.ValidResponses.Where(x => x.BodyField is not null))
{
if (httpMethodInfo.ResponseHeaderFields.Count != 0)
{
code.WriteLineSkipOnce();
foreach (var field in httpMethodInfo.ResponseHeaderFields)
code.WriteLine($"if (result.value.{field.ServiceField.Name} != null) res.header('{field.Name}', result.value.{field.ServiceField.Name});");
}
handledResponses.Remove(validResponse);
var bodyField = validResponse.BodyField!;
var statusCode = (int) validResponse.StatusCode;

var handledResponses = httpMethodInfo.ValidResponses.ToList();
foreach (var validResponse in httpMethodInfo.ValidResponses.Where(x => x.BodyField is not null))
code.WriteLineSkipOnce();
using (code.Block($"if (result.value.{bodyField.ServiceField.Name}) {{", "}"))
{
handledResponses.Remove(validResponse);
var bodyField = validResponse.BodyField!;
var statusCode = (int) validResponse.StatusCode;

code.WriteLineSkipOnce();
using (code.Block($"if (result.value.{bodyField.ServiceField.Name}) {{", "}"))
{
var bodyFieldType = service.GetFieldType(bodyField.ServiceField)!;
if (bodyFieldType.Kind == ServiceTypeKind.Boolean)
{
code.WriteLine($"res.status({statusCode});");
code.WriteLine("return;");
}
else
{
code.WriteLine($"res.status({statusCode}).send(result.value.{bodyField.ServiceField.Name});");
code.WriteLine("return;");
}
}
}

if (handledResponses.Count == 1)
{
var lastValidResponse = handledResponses[0];
var statusCode = (int) lastValidResponse.StatusCode;

if (lastValidResponse.NormalFields?.Count > 0)
var bodyFieldType = service.GetFieldType(bodyField.ServiceField)!;
if (bodyFieldType.Kind == ServiceTypeKind.Boolean)
{
code.WriteLineSkipOnce();
code.WriteLine($"res.status({statusCode}).send(result.value);");
code.WriteLine($"res.status({statusCode});");
code.WriteLine("return;");
}
else
{
code.WriteLineSkipOnce();
code.WriteLine($"res.status({statusCode});");
code.WriteLine($"res.status({statusCode}).send(result.value.{bodyField.ServiceField.Name});");
code.WriteLine("return;");
}
code.WriteLine("return;");
}
else if (handledResponses.Count > 1)
}

if (handledResponses.Count == 1)
{
var lastValidResponse = handledResponses[0];
var statusCode = (int) lastValidResponse.StatusCode;

if (lastValidResponse.NormalFields?.Count > 0)
{
throw new InvalidOperationException("More than one response is left.");
code.WriteLineSkipOnce();
code.WriteLine($"res.status({statusCode}).send(result.value);");
}
else
{
code.WriteLineSkipOnce();
code.WriteLine($"res.status({statusCode});");
}
code.WriteLine("return;");
}
else if (handledResponses.Count > 1)
{
throw new InvalidOperationException("More than one response is left.");
}

code.WriteLine();
code.WriteLine("throw new Error('Result must have an error or value.');");
}

code.WriteLine();
code.WriteLine("throw new Error('Result must have an error or value.');");
}
}
}
}

if (TypeScript)
WriteTypes(code, httpServiceInfo);
}));
}

return new CodeGenOutput(namedTexts, new List<CodeGenPattern>());
}
if (TypeScript)
WriteTypes(code, httpServiceInfo);
});

/// <summary>
/// Applies generator-specific settings.
/// </summary>
public override void ApplySettings(FileGeneratorSettings settings)
{
var ourSettings = (JavaScriptGeneratorSettings) settings;
ModuleName = ourSettings.ModuleName;
TypeScript = ourSettings.TypeScript;
Express = ourSettings.Express;
Fastify = ourSettings.Fastify;
DisableESLint = ourSettings.DisableESLint;
return new CodeGenOutput(file);
}

/// <summary>
/// Supports writing output to a single file.
/// </summary>
public override bool SupportsSingleOutput => true;

private void WriteFileHeader(CodeWriter code)
{
code.WriteLine($"// DO NOT EDIT: generated by {GeneratorName}");
Expand Down
Loading