diff --git a/conformance/ts/src/conformanceApi.ts b/conformance/ts/src/conformanceApi.ts index e18424a..8c1e794 100644 --- a/conformance/ts/src/conformanceApi.ts +++ b/conformance/ts/src/conformanceApi.ts @@ -2,7 +2,7 @@ /* eslint-disable */ import { HttpClientUtility, IServiceResult, IHttpClientOptions } from 'facility-core'; -import { IConformanceApi, IGetApiInfoRequest, IGetApiInfoResponse, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IMirrorFieldsRequest, IMirrorFieldsResponse, ICheckQueryRequest, ICheckQueryResponse, ICheckPathRequest, ICheckPathResponse, IMirrorHeadersRequest, IMirrorHeadersResponse, IMixedRequest, IMixedResponse, IRequiredRequest, IRequiredResponse, IMirrorBytesRequest, IMirrorBytesResponse, IMirrorTextRequest, IMirrorTextResponse, IBodyTypesRequest, IBodyTypesResponse, IWidget, IAny, IAnyArray, IAnyMap, IAnyResult, IAnyNullable, IHasWidget, Answer } from './conformanceApiTypes'; +import { IConformanceApi, IGetApiInfoRequest, IGetApiInfoResponse, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IMirrorFieldsRequest, IMirrorFieldsResponse, ICheckQueryRequest, ICheckQueryResponse, ICheckPathRequest, ICheckPathResponse, IMirrorHeadersRequest, IMirrorHeadersResponse, IMixedRequest, IMixedResponse, IRequiredRequest, IRequiredResponse, IMirrorBytesRequest, IMirrorBytesResponse, IMirrorTextRequest, IMirrorTextResponse, IBodyTypesRequest, IBodyTypesResponse, IWidget, IAny, IAnyArray, IAnyMap, IAnyResult, IAnyNullable, IHasWidget, Answer, ApiErrors } from './conformanceApiTypes'; export * from './conformanceApiTypes'; /** Provides access to ConformanceApi over HTTP via fetch. */ diff --git a/conformance/ts/src/conformanceApiTypes.ts b/conformance/ts/src/conformanceApiTypes.ts index 42586af..c321db2 100644 --- a/conformance/ts/src/conformanceApiTypes.ts +++ b/conformance/ts/src/conformanceApiTypes.ts @@ -515,3 +515,12 @@ export enum Answer { maybe = 'maybe', } +/** Custom errors. */ +export enum ApiErrors { + /** The user is not an administrator. */ + NotAdmin = 'NotAdmin', + + /** I'm "too" 😄! */ + TooHappy = 'TooHappy', +} + diff --git a/example/js/exampleApiServer.js b/example/js/exampleApiServer.js index 5ba997a..88aed80 100644 --- a/example/js/exampleApiServer.js +++ b/example/js/exampleApiServer.js @@ -16,6 +16,7 @@ const standardErrorCodes = { 'TooManyRequests': 429, 'InternalError': 500, 'ServiceUnavailable': 503, + 'NotAdmin': 403, }; function parseBoolean(value) { diff --git a/example/ts/src/exampleApi.ts b/example/ts/src/exampleApi.ts index de5424a..fd6775b 100644 --- a/example/ts/src/exampleApi.ts +++ b/example/ts/src/exampleApi.ts @@ -2,7 +2,7 @@ /* eslint-disable */ import { HttpClientUtility, IServiceResult, IHttpClientOptions } from 'facility-core'; -import { IExampleApi, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IEditWidgetRequest, IEditWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IGetWidgetWeightRequest, IGetWidgetWeightResponse, IGetPreferenceRequest, IGetPreferenceResponse, ISetPreferenceRequest, ISetPreferenceResponse, IGetInfoRequest, IGetInfoResponse, INotRestfulRequest, INotRestfulResponse, IKitchenRequest, IKitchenResponse, IWidget, IWidgetJob, IPreference, IObsoleteData, IKitchenSink, WidgetField, ObsoleteEnum } from './exampleApiTypes'; +import { IExampleApi, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IEditWidgetRequest, IEditWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IGetWidgetWeightRequest, IGetWidgetWeightResponse, IGetPreferenceRequest, IGetPreferenceResponse, ISetPreferenceRequest, ISetPreferenceResponse, IGetInfoRequest, IGetInfoResponse, INotRestfulRequest, INotRestfulResponse, IKitchenRequest, IKitchenResponse, IWidget, IWidgetJob, IPreference, IObsoleteData, IKitchenSink, WidgetField, ObsoleteEnum, ExampleApiErrors } from './exampleApiTypes'; export * from './exampleApiTypes'; /** Provides access to ExampleApi over HTTP via fetch. */ diff --git a/example/ts/src/exampleApiServer.ts b/example/ts/src/exampleApiServer.ts index 0609e3e..ac3ac2f 100644 --- a/example/ts/src/exampleApiServer.ts +++ b/example/ts/src/exampleApiServer.ts @@ -4,7 +4,7 @@ import * as bodyParser from 'body-parser'; import * as express from 'express'; import { IServiceResult } from 'facility-core'; -import { IExampleApi, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IEditWidgetRequest, IEditWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IGetWidgetWeightRequest, IGetWidgetWeightResponse, IGetPreferenceRequest, IGetPreferenceResponse, ISetPreferenceRequest, ISetPreferenceResponse, IGetInfoRequest, IGetInfoResponse, INotRestfulRequest, INotRestfulResponse, IKitchenRequest, IKitchenResponse, IWidget, IWidgetJob, IPreference, IObsoleteData, IKitchenSink, WidgetField, ObsoleteEnum } from './exampleApiTypes'; +import { IExampleApi, IGetWidgetsRequest, IGetWidgetsResponse, ICreateWidgetRequest, ICreateWidgetResponse, IGetWidgetRequest, IGetWidgetResponse, IDeleteWidgetRequest, IDeleteWidgetResponse, IEditWidgetRequest, IEditWidgetResponse, IGetWidgetBatchRequest, IGetWidgetBatchResponse, IGetWidgetWeightRequest, IGetWidgetWeightResponse, IGetPreferenceRequest, IGetPreferenceResponse, ISetPreferenceRequest, ISetPreferenceResponse, IGetInfoRequest, IGetInfoResponse, INotRestfulRequest, INotRestfulResponse, IKitchenRequest, IKitchenResponse, IWidget, IWidgetJob, IPreference, IObsoleteData, IKitchenSink, WidgetField, ObsoleteEnum, ExampleApiErrors } from './exampleApiTypes'; export * from './exampleApiTypes'; const standardErrorCodes: { [code: string]: number } = { @@ -18,6 +18,7 @@ const standardErrorCodes: { [code: string]: number } = { 'TooManyRequests': 429, 'InternalError': 500, 'ServiceUnavailable': 503, + 'NotAdmin': 403, }; function parseBoolean(value: string | undefined) { diff --git a/example/ts/src/exampleApiTypes.ts b/example/ts/src/exampleApiTypes.ts index ef70dcb..45ce934 100644 --- a/example/ts/src/exampleApiTypes.ts +++ b/example/ts/src/exampleApiTypes.ts @@ -350,3 +350,9 @@ export enum ObsoleteEnum { unused = 'unused', } +/** Custom errors. */ +export enum ExampleApiErrors { + /** The user is not an administrator. */ + NotAdmin = 'NotAdmin', +} + diff --git a/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs b/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs index 514608c..f8c4e58 100644 --- a/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs +++ b/src/Facility.CodeGen.JavaScript/JavaScriptGenerator.cs @@ -158,6 +158,22 @@ public override CodeGenOutput GenerateOutput(ServiceInfo service) } } + foreach (var errorSetInfo in service.ErrorSets) + { + typeNames.Add(errorSetInfo.Name); + code.WriteLine(); + WriteJsDoc(code, errorSetInfo); + using (code.Block($"export enum {errorSetInfo.Name} {{", "}")) + { + foreach (var error in errorSetInfo.Errors) + { + code.WriteLineSkipOnce(); + WriteJsDoc(code, error); + code.WriteLine($"{error.Name} = '{error.Name}',"); + } + } + } + code.WriteLine(); })); } @@ -440,6 +456,14 @@ public override CodeGenOutput GenerateOutput(ServiceInfo service) code.WriteLine("'TooManyRequests': 429,"); code.WriteLine("'InternalError': 500,"); code.WriteLine("'ServiceUnavailable': 503,"); + + foreach (var errorSetInfo in httpServiceInfo.ErrorSets) + { + foreach (var error in errorSetInfo.Errors) + { + code.WriteLine($"'{error.ServiceError.Name}': {(int) error.StatusCode},"); + } + } } // TODO: export this from facility-core? diff --git a/tests/Facility.CodeGen.JavaScript.UnitTests/JavaScriptGeneratorTests.cs b/tests/Facility.CodeGen.JavaScript.UnitTests/JavaScriptGeneratorTests.cs index 1133267..e9fa96e 100644 --- a/tests/Facility.CodeGen.JavaScript.UnitTests/JavaScriptGeneratorTests.cs +++ b/tests/Facility.CodeGen.JavaScript.UnitTests/JavaScriptGeneratorTests.cs @@ -246,6 +246,37 @@ public void GenerateExampleApiTypeScript_ExternEnumNameIsSameAsAlias() StringAssert.Contains("thing?: Thing;", typesFile.Text); } + [Test] + public void GenerateExampleApiTypeScript_IncludesErrorSets() + { + ServiceInfo service; + const string fileName = "Facility.CodeGen.JavaScript.UnitTests.ExampleApi.fsd"; + var parser = new FsdParser(); + var stream = GetType().GetTypeInfo().Assembly.GetManifestResourceStream(fileName)!; + Assert.IsNotNull(stream); + using (var reader = new StreamReader(stream)) + service = parser.ParseDefinition(new ServiceDefinitionText(Path.GetFileName(fileName), reader.ReadToEnd())); + + var generator = new JavaScriptGenerator + { + GeneratorName = "JavaScriptGeneratorTests", + TypeScript = true, + NewLine = "\n", + }; + var result = generator.GenerateOutput(service); + Assert.IsNotNull(result); + + var typesFile = result.Files.Single(f => f.Name == "exampleApiTypes.ts"); + const string expectedErrorSet = """ + /** Custom errors. */ + export enum ExampleApiErrors { + /** The user is not an administrator. */ + NotAdmin = 'NotAdmin', + } + """; + Assert.That(typesFile.Text, Contains.Substring(expectedErrorSet)); + } + private void ThrowsServiceDefinitionException(string definition, string message) { var parser = new FsdParser();