RicoSuter / NSwag

The Swagger/OpenAPI toolchain for .NET, ASP.NET Core and TypeScript.
http://NSwag.org
MIT License
6.78k stars 1.29k forks source link

Injection of current folder path into TypeScript file #3172

Open vozeldr opened 3 years ago

vozeldr commented 3 years ago

I'm using nswag run /runtime:NetCore31 with the below nswag.json config file. In the settings for the openApiToTypeScriptClient code generator, when I added the value for extensionCode it does prepend the imports for the base classes to the file, but at the end of the file, just before the closing brace, it also injects a line of code that is the current file path (which results in code that doesn't work).

{
  "runtime": "NetCore31",
  "defaultVariables": "Configuration=Release",
  "documentGenerator": {
    "webApiToOpenApi": {
      "defaultReferenceTypeNullHandling": "Null",
      "defaultDictionaryValueReferenceTypeNullHandling": "NotNull",
      "generateAbstractProperties": false,
      "flattenInheritanceHierarchy": false,
      "generateAbstractSchemas": true,
      "generateKnownTypes": true,
      "generateXmlObjects": false,
      "ignoreObsoleteProperties": false,
      "allowReferencesWithProperties": false,
      "generateEnumMappingDescription": true,
      "alwaysAllowAdditionalObjectProperties": false,
      "generateExamples": false,
      "schemaType": "OpenApi3",
      "serializerSettings": {
        "referenceLoopHandling": "Ignore",
        "maxDepth": 2
      },
      "serializerOptions": {},
      "excludedTypeNames": [],
      "defaultPropertyNameHandling": "Default",
      "defaultEnumHandling": "Integer",
      "title": "API",
      "description": "",
      "version": "1.0.0",
      "allowNullableBodyParameters": true,
      "defaultResponseReferenceTypeNullHandling": "Null",
      "useControllerSummaryAsTagDescription": true,
      "defaultUrlTemplate": "{controller}/{id?}",
      "isAspNetCore": true,
      "addMissingPathParameters": false,
      "controllerNames": [],
      "assemblyPaths": ["../bin/$(Configuration)/netcoreapp3.1/bootstrap.dll"],
      "referencePaths": ["../bin/$(Configuration)/netcoreapp3.1/"],
      "output": "swagger.json",
      "outputType": "OpenApi3"
    }
  },
  "codeGenerators": {
    "openApiToTypeScriptClient": {
      "codeGeneratorSettings": {},
      "className": "{controller}Client",
      "generateDtoTypes": true,
      "generateClientInterfaces": true,
      "generateClientClasses": true,
      "generateOptionalParameters": false,
      "excludedParameterNames": [],
      "wrapResponses": false,
      "wrapResponseMethods": [],
      "generateResponseClasses": true,
      "responseClass": "SwaggerResponse",
      "template": "Axios",
      "promiseType": "Promise",
      "wrapDtoExceptions": false,
      "clientBaseClass": "BaseClient",
      "configurationClass": "ClientOptions",
      "useTransformOptionsMethod": true,
      "useTransformResultMethod": true,
      "baseUrlTokenName": "API_BASE_URL",
      "protectedMethods": [],
      "importRequiredTypes": true,
      "useGetBaseUrlMethod": true,
      "queryNullValue": "",
      "exceptionClass": "ApiException",
      "useAbortSignal": false,
      "httpClass": "HttpClient",
      "withCredentials": true,
      "rxJsVersion": 6.0,
      "useSingletonProvider": false,
      "injectionTokenType": "OpaqueToken",
      "moduleName": "MyApi",
      "namespace": "",
      "typeScriptVersion": 2.7,
      "dateTimeType": "Date",
      "nullValue": "Undefined",
      "exportTypes": true,
      "operationGenerationMode": "MultipleClientsFromOperationId",
      "markOptionalProperties": true,
      "generateCloneMethod": false,
      "typeStyle": "Class",
      "enumStyle": "Enum",
      "useLeafType": false,
      "classTypes": [],
      "extendedClasses": [],
      "extensionCode": "import { BaseClient, ClientOptions } from './base';",
      "generateDefaultValues": true,
      "excludedTypeNames": [],
      "handleReferences": false,
      "generateConstructorInterface": true,
      "convertConstructorInterfaceData": false,
      "inlineNamedDictionaries": false,
      "inlineNamedAny": false,
      "templateDirectory": null,
      "typeNameGeneratorType": null,
      "propertyNameGeneratorType": null,
      "enumNameGeneratorType": null,
      "serviceHost": null,
      "serviceSchemes": null,
      "output": "src/index.ts",
      "newLineBehavior": "Auto"
    }
  }
}

The command-line output looks like this:

> nswag run /runtime:NetCore31

NSwag NPM CLI
NSwag command line tool for .NET Core NetCore31, toolchain v13.9.2.0 (NJsonSchema v10.3.1.0 (Newtonsoft.Json v12.0.0.0))
Visit http://NSwag.org for more information.
NSwag bin directory: /home/drv/Repositories/path/node_modules/nswag/bin/binaries/NetCore31

Executing file 'nswag.json' with variables ''...
Done.

Duration: 00:00:01.9931161

The end of the generated typescript file looks like this:

/* tslint:disable */
/* eslint-disable */
//----------------------
// <auto-generated>
//     Generated using the NSwag toolchain v13.9.2.0 (NJsonSchema v10.3.1.0 (Newtonsoft.Json v12.0.0.0)) (http://NSwag.org)
// </auto-generated>
//----------------------
// ReSharper disable InconsistentNaming

import { BaseClient, ClientOptions } from './base';
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelToken } from 'axios';

export module MyApi {

//... about 10,400 lines of typescript

function isAxiosError(obj: any | undefined): obj is AxiosError {
    return obj && obj.isAxiosError === true;
}

/home/drv/Repositories/path/
}
vozeldr commented 3 years ago

This problem is still occurring and can be replicated (100% of the time) on another developer's machine running Windows. I have a hacky workaround to the problem, but it would be great to have it fixed.

My package.json has the following scripts:

"generate": "nswag run /runtime:NetCore31",
"postgenerate": "node fix-index.js",

And then fix-index.js does the following:

const path = require('path');
const fs = require('fs');

/**
 * For some reason, when nswag generates the index.ts file, it is placing the current directory path
 * as the next-to-last line of the file, which is invalid javascript since it's like /home/...
 * This removes lines that start with a / not immediately followed by another / or a *
 */
function removeBadLineFromGeneratedFile() {
    const file = path.resolve(__dirname, 'src', 'index.ts');
    fs.readFile(file, 'utf-8', (err, data) => {
        if (err) throw new Error('Failed to read index.ts.');

        const inputLines = data.toString().split('\n');
        const outputLines = [];
        inputLines.forEach((line) => {
            if (line.toLowerCase().startsWith('c:') ||
                (line.startsWith('/') && (line.charAt(1) !== '/' && line.charAt(1) !== '*'))) {
                return;
            }

            outputLines.push(line);
        });

        fs.writeFile(file, outputLines.join('\n'), 'utf-8', (err) => {
            if (err) throw new Error('Failed to write index.ts');
        });
    });
}

removeBadLineFromGeneratedFile(); 
jeremyVignelles commented 3 years ago

could it be caused by the extensionCode parameter? does it create that line without extensionCode? what about an extension file?

vozeldr commented 3 years ago

Yes, it creates the extensionCode at the top of the file to inject my import statement. I'm not sure about an extension file. The problem did not occur before I added the extensionCode value.

I just verified the problem still exists with the 13.10.5 version of nswag.

jeremyVignelles commented 3 years ago

Indeed, but if I remember correctly, the extension code is split : the imports are placed at the top while the rest is placed at the bottom. I suspect something is wrong here, as it's probably not-so-commonly used. Could you try to remove the extensionCode property completely? EDIT: other suggestion, could you try to add // to the end of the extension code and see what's generated?

vozeldr commented 3 years ago

Yes, it is definitely using the extensionCode property that introduced the issue I reported. Another interesting thing is that on Windows, when it inserts the import statement it changes './base' to '\base' which is another bug.

I changed my usage to instead have the fix-index postgenerate script insert the import statement and I stopped using extensionCode.

robertkemp commented 3 years ago

Taking some inspiration from @jeremyVignelles... Adding // to the beginning of the extensionCode value resolved the issue.

vozeldr commented 3 years ago

Adding // to the beginning (or the end) didn't work for me... but adding /* fix bug */ to the beginning did. Resulting line for extension code looks like this:

"extensionCode": "/* fix bug */ import { BaseClient, ClientOptions } from './base';",

This adds the import to the top where it belongs and then adds a line with the comment before the closing bracket in the file:

/* fix bug */
}
RicoSuter commented 3 years ago

I suspect something is wrong here, as it's probably not-so-commonly used.

That's well possible as I also do not use this anymore... would need to be tested and fixed in NJsonSchema

omykhayl commented 10 months ago

I'm wondering why it is not fixed during 4 years :) ? And how is the NSwag usable at all if I cannot reference my custom code for token injection, etc.? This is important issue and it is 100% reproducible

lahma commented 10 months ago

@omykhayl we have been waiting for a brave soul like you who will implement it, please create a PR with the missing functionality.