Repository: lukeautry/tsoa (ts)

Issue:
Composite types give error on swagger codegen
I realize this format was already changed once but the tildes (~) in the generated model names cause swagger code gen to error out.

## Sorting

* **I'm submitting a ...**
  - [x] bug report
  - [ ] feature request
  - [ ] support request

* I confirm that I
  - [x] used the [search](https://github.com/lukeautry/tsoa/search?type=Issues) to make sure that a similar issue hasn't already been submit

## Expected Behavior
<!--- Tell us what should happen -->
Generated swagger spec should be able to be used in swagger code gen without ignoring errors.

<!--- If this is related to swagger/OpenAPI files, then please include swagger.yaml that you wish tsoa had generated  -->
```json
...
"schemas": {
    "Pick_Athlete_Exclude_keyofAthlete_id__OR__creationDate__OR__modificationDate__OR__fullName__": {
        "....": "..."
    }
}
```


## Current Behavior
<!--- Tell us what happens instead of the expected behavior -->
<!--- If this is related to swagger/OpenAPI files, then please include swagger.yaml that tsoa actually generated  -->
```json
...
"schemas": {
    "Pick_Athlete.Exclude_keyofAthlete.id~OR~creationDate~OR~modificationDate~OR~fullName__": {
        "....": "..."
    }
}
```

## Possible Solution
I know that this problem has been thought about a lot considering the changes since V3 so I won't belabor this.  I've seen some of the other bug reports about why it was changed.  I just wanted to point out that swagger codegen wants each name to match this regex: `^[a-zA-Z0-9\.\- _]+$` which makes it seem like only dash or underscore or doubles of those would work as a separator character which won't cause errors.  Another potential solution would be to allow users to override those crazy names somehow with their own custom values.

## Steps to Reproduce
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->
1.  Use composite types for a controller
2.  generate the swagger document
3.  use swagger-codegen to point at that generated document.

```
$ node_modules/\@openapitools/openapi-generator-cli/bin/openapi-generator generate -i http://localhost:5000/swagger.json --generator-name swift5 -o swift-app

[main] WARN  o.o.codegen.utils.ModelUtils - [deprecated] inheritance without use of 'discriminator.propertyName' is deprecated and will be removed in a future release.
Generating model for null
Exception in thread "main" org.openapitools.codegen.SpecValidationException: There were issues with the specification. The option can be disabled via validateSpec
(Maven/Gradle) or --skip-validate-spec (CLI).
 | Error count: 2, Warning count: 30
Errors:
        -attribute components.schemas.Schema name Pick_Athlete.Exclude_keyofAthlete.id~OR~creationDate~OR~modificationDate~OR~fullName__ doesn't adhere to regular
expression ^[a-zA-Z0-9\.\-_]+$
        -attribute components.schemas.Schema name Omit_Athlete.id~OR~creationDate~OR~modificationDate~OR~fullName_ doesn't adhere to regular expression ^[a-zA-Z0-9\.\-
_]+$
Warnings:
        -attribute components.schemas.Schema name Pick_Athlete.Exclude_keyofAthlete.id~OR~creationDate~OR~modificationDate~OR~fullName__ doesn't adhere to regular
expression ^[a-zA-Z0-9\.\-_]+$
        -attribute components.schemas.Schema name Omit_Athlete.id~OR~creationDate~OR~modificationDate~OR~fullName_ doesn't adhere to regular expression ^[a-zA-Z0-9\.\-
_]+$
        at org.openapitools.codegen.config.CodegenConfigurator.toContext(CodegenConfigurator.java:471)
        at org.openapitools.codegen.config.CodegenConfigurator.toClientOptInput(CodegenConfigurator.java:498)
        at org.openapitools.codegen.cmd.Generate.run(Generate.java:415)
        at org.openapitools.codegen.OpenAPIGenerator.main(OpenAPIGenerator.java:61)
```

## Context (Environment)
Version of the library: 3.0.4
Version of NodeJS: 12.12.0
* Confirm you were using yarn not npm: [x]

## Breaking change?
No because V3 is still in beta

Pull request:
Schema Naming: Avoid tilde, escape double quotes properly
### All Submissions:

- [x] Have you followed the guidelines in our [Contributing](https://github.com/lukeautry/tsoa/tree/master/docs/CONTRIBUTING.md) document?
- [x] Have you checked to ensure there aren't other open [Pull Requests](https://github.com/lukeautry/tsoa/pulls) for the same update/change?
- [x] Have you written unit tests?
- [x] Have you written unit tests that cover the negative cases (i.e.: if bad data is submitted, does the library respond properly)?
- [x] This PR is associated with an existing issue?

**Closing issues**
closes #649 

### If this is a new feature submission:

- [x] Has the issue had a maintainer respond to the issue and clarify that the feature is something that aligns with the [goals](https://github.com/lukeautry/tsoa#goal) and [philosophy](https://github.com/lukeautry/tsoa#philosophy) of the project?

**Potential Problems With The Approach**
Breaking Change


Patch:
diff --git a/src/metadataGeneration/typeResolver.ts b/src/metadataGeneration/typeResolver.ts
index 900784d9..b27f0408 100644
--- a/src/metadataGeneration/typeResolver.ts
+++ b/src/metadataGeneration/typeResolver.ts
@@ -555,14 +555,15 @@ export class TypeResolver {
         .replace(/<|>/g, '_')
         .replace(/\s+/g, '')
         .replace(/,/g, '.')
-        .replace(/\'([^']*)\'|\"(^"*)\"/g, '$1')
-        .replace(/&/g, '~AND~')
-        .replace(/\|/g, '~OR~')
-        .replace(/\[\]/g, 'Array')
+        .replace(/\'([^']*)\'/g, '$1')
+        .replace(/\"([^"]*)\"/g, '$1')
+        .replace(/&/g, '-and-')
+        .replace(/\|/g, '-or-')
+        .replace(/\[\]/g, '-Array')
         .replace(/{|}/g, '_') // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__
         .replace(/([a-z]+):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_
         .replace(/;/g, '--')
-        .replace(/([a-z]+)\[([a-z]+)\]/gi, '$1~$2~'), // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_,
+        .replace(/([a-z]+)\[([a-z]+)\]/gi, '$1-at-$2'), // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_,
     );
   }
 


Test patch:
diff --git a/tests/fixtures/testModel.ts b/tests/fixtures/testModel.ts
index 61345d9e..dc6672f2 100644
--- a/tests/fixtures/testModel.ts
+++ b/tests/fixtures/testModel.ts
@@ -95,7 +95,8 @@ export interface TestModel extends Model {
     partial?: Partial<Account>;
     excludeToEnum?: Exclude<EnumUnion, EnumNumberValue>;
     excludeToAlias?: Exclude<ThreeOrFour, TypeAliasModel3>;
-    excludeLiteral?: Exclude<keyof TestClassModel, 'account' | 'defaultValue2' | 'indexedTypeToInterface' | 'indexedTypeToClass' | 'indexedTypeToAlias'>;
+    // prettier-ignore
+    excludeLiteral?: Exclude<keyof TestClassModel, 'account' | "defaultValue2" | "indexedTypeToInterface" | 'indexedTypeToClass' | 'indexedTypeToAlias'>;
     excludeToInterface?: Exclude<OneOrTwo, TypeAliasModel1>;
     excludeTypeToPrimitive?: NonNullable<number | null>;
 
diff --git a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
index 12371ec9..e0958a45 100644
--- a/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
+++ b/tests/unit/swagger/definitionsGeneration/definitions.spec.ts
@@ -403,11 +403,11 @@ describe('Definition generation', () => {
             expect(propertySchema.$ref).to.eq('#/definitions/Generic__foo-string--bar-boolean__');
           },
           indexed: (propertyName, propertySchema) => {
-            expect(propertySchema.$ref).to.eq('#/definitions/Partial_Indexed~foo~_');
+            expect(propertySchema.$ref).to.eq('#/definitions/Partial_Indexed-at-foo_');
           },
           record: (propertyName, propertySchema) => {
-            expect(propertySchema.$ref).to.eq('#/definitions/Record_record-foo~OR~record-bar._data-string__');
-            const schema = getValidatedDefinition('Record_record-foo~OR~record-bar._data-string__', currentSpec);
+            expect(propertySchema.$ref).to.eq('#/definitions/Record_record-foo-or-record-bar._data-string__');
+            const schema = getValidatedDefinition('Record_record-foo-or-record-bar._data-string__', currentSpec);
             expect(schema).to.be.deep.eq({
               properties: {
                 'record-foo': {
@@ -492,13 +492,13 @@ describe('Definition generation', () => {
             expect(propertySchema.$ref).to.eq('#/definitions/GenericRequest_Array_TypeAliasModel1__', `for property ${propertyName}.$ref`);
           },
           genericNestedArrayCharacter1: (propertyName, propertySchema) => {
-            expect(propertySchema.$ref).to.eq('#/definitions/GenericRequest_TypeAliasModel1Array_', `for property ${propertyName}.$ref`);
+            expect(propertySchema.$ref).to.eq('#/definitions/GenericRequest_TypeAliasModel1-Array_', `for property ${propertyName}.$ref`);
           },
           genericNestedArrayKeyword2: (propertyName, propertySchema) => {
             expect(propertySchema.$ref).to.eq('#/definitions/GenericRequest_Array_TypeAliasModel2__', `for property ${propertyName}.$ref`);
           },
           genericNestedArrayCharacter2: (propertyName, propertySchema) => {
-            expect(propertySchema.$ref).to.eq('#/definitions/GenericRequest_TypeAliasModel2Array_', `for property ${propertyName}.$ref`);
+            expect(propertySchema.$ref).to.eq('#/definitions/GenericRequest_TypeAliasModel2-Array_', `for property ${propertyName}.$ref`);
           },
           defaultGenericModel: (propertyName, propertySchema) => {
             expect(propertySchema.$ref).to.eq('#/definitions/GenericModel', `for property ${propertyName}.$ref`);
@@ -679,13 +679,13 @@ describe('Definition generation', () => {
                   excludeToEnum: { $ref: '#/definitions/Exclude_EnumUnion.EnumNumberValue_', description: undefined, format: undefined, example: undefined },
                   excludeToAlias: { $ref: '#/definitions/Exclude_ThreeOrFour.TypeAliasModel3_', description: undefined, format: undefined, example: undefined },
                   excludeLiteral: {
-                    $ref: '#/definitions/Exclude_keyofTestClassModel.account~OR~defaultValue2~OR~indexedTypeToInterface~OR~indexedTypeToClass~OR~indexedTypeToAlias_',
+                    $ref: '#/definitions/Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias_',
                     description: undefined,
                     format: undefined,
                     example: undefined,
                   },
                   excludeToInterface: { $ref: '#/definitions/Exclude_OneOrTwo.TypeAliasModel1_', description: undefined, format: undefined, example: undefined },
-                  excludeTypeToPrimitive: { $ref: '#/definitions/NonNullable_number~OR~null_', description: undefined, format: undefined, example: undefined },
+                  excludeTypeToPrimitive: { $ref: '#/definitions/NonNullable_number-or-null_', description: undefined, format: undefined, example: undefined },
                   pick: { $ref: '#/definitions/Pick_ThingContainerWithTitle_string_.list_', description: undefined, format: undefined, example: undefined },
                   readonlyClass: { $ref: '#/definitions/Readonly_TestClassModel_', description: undefined, format: undefined, example: undefined },
                   defaultArgs: { $ref: '#/definitions/DefaultTestModel', description: undefined, format: undefined, example: undefined },
@@ -806,7 +806,7 @@ describe('Definition generation', () => {
               `for definition linked by ${propertyName}`,
             );
 
-            const excludeLiteral = getValidatedDefinition('Exclude_keyofTestClassModel.account~OR~defaultValue2~OR~indexedTypeToInterface~OR~indexedTypeToClass~OR~indexedTypeToAlias_', currentSpec);
+            const excludeLiteral = getValidatedDefinition('Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias_', currentSpec);
             expect(excludeLiteral).to.deep.eq(
               {
                 type: 'string',
@@ -846,7 +846,7 @@ describe('Definition generation', () => {
               `for definition linked by ${propertyName}`,
             );
 
-            const excludeTypeToPrimitive = getValidatedDefinition('NonNullable_number~OR~null_', currentSpec);
+            const excludeTypeToPrimitive = getValidatedDefinition('NonNullable_number-or-null_', currentSpec);
             expect(excludeTypeToPrimitive).to.deep.eq(
               {
                 type: 'number',
@@ -1290,7 +1290,7 @@ describe('Definition generation', () => {
           expect(property.$ref).to.equal('#/definitions/TestModel');
         });
         it('should generate different definitions for a generic model array', () => {
-          const definition = getValidatedDefinition('GenericModel_TestModelArray_', currentSpec).properties;
+          const definition = getValidatedDefinition('GenericModel_TestModel-Array_', currentSpec).properties;
 
           if (!definition) {
             throw new Error(`There were no properties on model.`);
@@ -1326,7 +1326,7 @@ describe('Definition generation', () => {
           expect(property.type).to.equal('string');
         });
         it('should generate different definitions for a generic primitive array', () => {
-          const definition = getValidatedDefinition('GenericModel_stringArray_', currentSpec).properties;
+          const definition = getValidatedDefinition('GenericModel_string-Array_', currentSpec).properties;
 
           if (!definition) {
             throw new Error(`There were no properties on model.`);
@@ -1348,13 +1348,13 @@ describe('Definition generation', () => {
           expect((property.items as Swagger.Schema).type).to.equal('string');
         });
         it('should propagate generics', () => {
-          const definition = getValidatedDefinition('GenericModel_TestModelArray_', currentSpec).properties;
+          const definition = getValidatedDefinition('GenericModel_TestModel-Array_', currentSpec).properties;
 
           expect(definition!.result).to.deep.equal({ items: { $ref: '#/definitions/TestModel' }, type: 'array', description: undefined, format: undefined, example: undefined, default: undefined });
           expect(definition!.union).to.deep.equal({ type: 'object', description: undefined, format: undefined, default: undefined, example: undefined });
-          expect(definition!.nested).to.deep.equal({ $ref: '#/definitions/GenericRequest_TestModelArray_', description: undefined, format: undefined, example: undefined });
+          expect(definition!.nested).to.deep.equal({ $ref: '#/definitions/GenericRequest_TestModel-Array_', description: undefined, format: undefined, example: undefined });
 
-          const ref = getValidatedDefinition('GenericRequest_TestModelArray_', currentSpec).properties;
+          const ref = getValidatedDefinition('GenericRequest_TestModel-Array_', currentSpec).properties;
           expect(ref!.name).to.deep.equal({ type: 'string', description: undefined, format: undefined, default: undefined, example: undefined });
           expect(ref!.value).to.deep.equal({ items: { $ref: '#/definitions/TestModel' }, type: 'array', description: undefined, format: undefined, default: undefined, example: undefined });
         });
@@ -1365,16 +1365,16 @@ describe('Definition generation', () => {
           expect(definition!.shouldBeString!.$ref).to.deep.equal('#/definitions/TSameNameDifferentValue');
         });
         it('should check heritage clauses for type args', () => {
-          const definition = getValidatedDefinition('GenericModel_TestModelArray_', currentSpec).properties;
+          const definition = getValidatedDefinition('GenericModel_TestModel-Array_', currentSpec).properties;
 
           expect(definition!.heritageCheck).to.deep.equal({
-            $ref: '#/definitions/ThingContainerWithTitle_TestModelArray_',
+            $ref: '#/definitions/ThingContainerWithTitle_TestModel-Array_',
             description: undefined,
             format: undefined,
             example: undefined,
           });
 
-          const ref = getValidatedDefinition('ThingContainerWithTitle_TestModelArray_', currentSpec).properties;
+          const ref = getValidatedDefinition('ThingContainerWithTitle_TestModel-Array_', currentSpec).properties;
           expect(ref!.title).to.deep.equal({ type: 'string', description: undefined, format: undefined, default: undefined, example: undefined });
           expect(ref!.t).to.deep.equal({
             default: undefined,
diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts
index 6c9bab9f..c67995b0 100644
--- a/tests/unit/swagger/schemaDetails3.spec.ts
+++ b/tests/unit/swagger/schemaDetails3.spec.ts
@@ -594,11 +594,11 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
             expect(propertySchema.$ref).to.eq('#/components/schemas/Generic__foo-string--bar-boolean__');
           },
           indexed: (propertyName, propertySchema) => {
-            expect(propertySchema.$ref).to.eq('#/components/schemas/Partial_Indexed~foo~_');
+            expect(propertySchema.$ref).to.eq('#/components/schemas/Partial_Indexed-at-foo_');
           },
           record: (propertyName, propertySchema) => {
-            expect(propertySchema.$ref).to.eq('#/components/schemas/Record_record-foo~OR~record-bar._data-string__');
-            const schema = getComponentSchema('Record_record-foo~OR~record-bar._data-string__', currentSpec);
+            expect(propertySchema.$ref).to.eq('#/components/schemas/Record_record-foo-or-record-bar._data-string__');
+            const schema = getComponentSchema('Record_record-foo-or-record-bar._data-string__', currentSpec);
             expect(schema).to.be.deep.eq({
               properties: {
                 'record-foo': {
@@ -683,13 +683,13 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
             expect(propertySchema.$ref).to.eq('#/components/schemas/GenericRequest_Array_TypeAliasModel1__', `for property ${propertyName}.$ref`);
           },
           genericNestedArrayCharacter1: (propertyName, propertySchema) => {
-            expect(propertySchema.$ref).to.eq('#/components/schemas/GenericRequest_TypeAliasModel1Array_', `for property ${propertyName}.$ref`);
+            expect(propertySchema.$ref).to.eq('#/components/schemas/GenericRequest_TypeAliasModel1-Array_', `for property ${propertyName}.$ref`);
           },
           genericNestedArrayKeyword2: (propertyName, propertySchema) => {
             expect(propertySchema.$ref).to.eq('#/components/schemas/GenericRequest_Array_TypeAliasModel2__', `for property ${propertyName}.$ref`);
           },
           genericNestedArrayCharacter2: (propertyName, propertySchema) => {
-            expect(propertySchema.$ref).to.eq('#/components/schemas/GenericRequest_TypeAliasModel2Array_', `for property ${propertyName}.$ref`);
+            expect(propertySchema.$ref).to.eq('#/components/schemas/GenericRequest_TypeAliasModel2-Array_', `for property ${propertyName}.$ref`);
           },
           defaultGenericModel: (propertyName, propertySchema) => {
             expect(propertySchema.$ref).to.eq('#/components/schemas/GenericModel', `for property ${propertyName}.$ref`);
@@ -848,13 +848,13 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
                   excludeToEnum: { $ref: '#/components/schemas/Exclude_EnumUnion.EnumNumberValue_', description: undefined, format: undefined, example: undefined },
                   excludeToAlias: { $ref: '#/components/schemas/Exclude_ThreeOrFour.TypeAliasModel3_', description: undefined, format: undefined, example: undefined },
                   excludeLiteral: {
-                    $ref: '#/components/schemas/Exclude_keyofTestClassModel.account~OR~defaultValue2~OR~indexedTypeToInterface~OR~indexedTypeToClass~OR~indexedTypeToAlias_',
+                    $ref: '#/components/schemas/Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias_',
                     description: undefined,
                     format: undefined,
                     example: undefined,
                   },
                   excludeToInterface: { $ref: '#/components/schemas/Exclude_OneOrTwo.TypeAliasModel1_', description: undefined, format: undefined, example: undefined },
-                  excludeTypeToPrimitive: { $ref: '#/components/schemas/NonNullable_number~OR~null_', description: undefined, format: undefined, example: undefined },
+                  excludeTypeToPrimitive: { $ref: '#/components/schemas/NonNullable_number-or-null_', description: undefined, format: undefined, example: undefined },
                   pick: { $ref: '#/components/schemas/Pick_ThingContainerWithTitle_string_.list_', description: undefined, format: undefined, example: undefined },
                   readonlyClass: { $ref: '#/components/schemas/Readonly_TestClassModel_', description: undefined, format: undefined, example: undefined },
                   defaultArgs: { $ref: '#/components/schemas/DefaultTestModel', description: undefined, format: undefined, example: undefined },
@@ -1067,7 +1067,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
               `for a schema linked by property ${propertyName}`,
             );
 
-            const excludeLiteral = getComponentSchema('Exclude_keyofTestClassModel.account~OR~defaultValue2~OR~indexedTypeToInterface~OR~indexedTypeToClass~OR~indexedTypeToAlias_', currentSpec);
+            const excludeLiteral = getComponentSchema('Exclude_keyofTestClassModel.account-or-defaultValue2-or-indexedTypeToInterface-or-indexedTypeToClass-or-indexedTypeToAlias_', currentSpec);
             expect(excludeLiteral).to.deep.eq(
               {
                 oneOf: [
@@ -1105,7 +1105,7 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
               `for a schema linked by property ${propertyName}`,
             );
 
-            const excludeTypeToPrimitive = getComponentSchema('NonNullable_number~OR~null_', currentSpec);
+            const excludeTypeToPrimitive = getComponentSchema('NonNullable_number-or-null_', currentSpec);
             expect(excludeTypeToPrimitive).to.deep.eq(
               {
                 type: 'number',