Repository: lukeautry/tsoa (ts)

Issue:
Can't set contact information
<!--- Provide a general summary of the issue in the Title above -->

## Sorting

- **I'm submitting a ...**

  - [ ] bug report
  - [x] 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

tsoa.json allows defining contact information as specified in OpenAPI.
Reference: [OpenAPI Contact Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#contactObject)

## Current Behavior

No way to set this.

## Possible Solution

Extend SpecConfig Interface: 

```ts
  /**
   * Contact Information
   */
  contact?: {
    /**
     * The identifying name of the contact person/organization.
     * @default npm package author
     */
    name?: string;

    /**
     * The email address of the contact person/organization.
     * @default npm package author email
     */
    email?: string;

    /**
     * API Info url
     * The URL pointing to the contact information.
     * @default npm package author url
     */
    url?: string;
  };

```

As shown in the comments, we can parse the npm package author information as a backup.

## Context (Environment)

Version of the library: 3.0.8
Version of NodeJS: 12

- Confirm you were using yarn not npm: [x]

## Breaking change?

No


Pull request description:
Feat: Contact information
### 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?
- [ ] 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 #709 
### 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?

Patch:
diff --git a/src/cli.ts b/src/cli.ts
index 6fb10701..6cdeec39 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -36,6 +36,7 @@ const determineNoImplicitAdditionalSetting = (noImplicitAdditionalProperties: Co
     return 'ignore';
   }
 };
+const authorInformation = getPackageJsonValue('author', 'unknown');
 
 const getConfig = async (configPath = 'tsoa.json'): Promise<Config> => {
   let config: Config;
@@ -100,6 +101,16 @@ export const validateSpecConfig = async (config: Config): Promise<ExtendedSpecCo
   config.spec.license = config.spec.license || (await licenseDefault());
   config.spec.basePath = config.spec.basePath || '/';
 
+  if (!config.spec.contact) {
+    config.spec.contact = {};
+  }
+
+  const contact = (await authorInformation).match(/^([^<(]*)?\s*(?:<([^>(]*)>)?\s*(?:\(([^)]*)\)|$)/m);
+
+  config.spec.contact.name = config.spec.contact.name || contact?.[1];
+  config.spec.contact.email = config.spec.contact.email || contact?.[2];
+  config.spec.contact.url = config.spec.contact.url || contact?.[3];
+
   return {
     ...config.spec,
     noImplicitAdditionalProperties,
diff --git a/src/config.ts b/src/config.ts
index b7c904c4..38b976ea 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -76,10 +76,34 @@ export interface SpecConfig {
   name?: string;
 
   /**
-   * 'API description; defaults to npm package description
+   * API description; defaults to npm package description
    */
   description?: string;
 
+  /**
+   * Contact Information
+   */
+  contact?: {
+    /**
+     * The identifying name of the contact person/organization.
+     * @default npm package author
+     */
+    name?: string;
+
+    /**
+     * The email address of the contact person/organization.
+     * @default npm package author email
+     */
+    email?: string;
+
+    /**
+     * API Info url
+     * The URL pointing to the contact information.
+     * @default npm package author url
+     */
+    url?: string;
+  };
+
   /**
    * API license; defaults to npm package license
    */
diff --git a/src/swagger/specGenerator2.ts b/src/swagger/specGenerator2.ts
index 6cc88a7b..0ad1dbee 100644
--- a/src/swagger/specGenerator2.ts
+++ b/src/swagger/specGenerator2.ts
@@ -44,6 +44,11 @@ export class SpecGenerator2 extends SpecGenerator {
     if (this.config.license) {
       spec.info.license = { name: this.config.license };
     }
+
+    if (this.config.contact) {
+      spec.info.contact = this.config.contact;
+    }
+
     if (this.config.spec) {
       this.config.specMerging = this.config.specMerging || 'immediate';
       const mergeFuncs: { [key: string]: any } = {
diff --git a/src/swagger/specGenerator3.ts b/src/swagger/specGenerator3.ts
index 0cac18ea..2622bd64 100644
--- a/src/swagger/specGenerator3.ts
+++ b/src/swagger/specGenerator3.ts
@@ -56,6 +56,11 @@ export class SpecGenerator3 extends SpecGenerator {
     if (this.config.license) {
       info.license = { name: this.config.license };
     }
+
+    if (this.config.contact) {
+      info.contact = this.config.contact;
+    }
+
     return info;
   }
 


Test patch:
diff --git a/tests/fixtures/defaultOptions.ts b/tests/fixtures/defaultOptions.ts
index cd8eca4b..a0d3f72b 100644
--- a/tests/fixtures/defaultOptions.ts
+++ b/tests/fixtures/defaultOptions.ts
@@ -13,6 +13,11 @@ export function getDefaultOptions(outputDirectory: string = '', entryFile: strin
       host: 'localhost:3000',
       license: 'MIT',
       name: 'Test API',
+      contact: {
+        email: 'jane@doe.com',
+        name: 'Jane Doe',
+        url: 'www.jane-doe.com',
+      },
       outputDirectory,
       securityDefinitions: {
         basic: {
diff --git a/tests/unit/swagger/schemaDetails.spec.ts b/tests/unit/swagger/schemaDetails.spec.ts
index 7c2505be..841ca099 100644
--- a/tests/unit/swagger/schemaDetails.spec.ts
+++ b/tests/unit/swagger/schemaDetails.spec.ts
@@ -55,7 +55,13 @@ describe('Schema details generation', () => {
     throw new Error('No license name.');
   }
 
+  const contactInfo = spec.info.contact;
+  if (!contactInfo) {
+    throw new Error('No contact information.');
+  }
+
   it('should set API license if provided', () => expect(licenseName).to.equal(getDefaultExtendedOptions().license));
+  it('should set contact information if provided', () => expect(contactInfo).to.deep.equal(getDefaultExtendedOptions().contact));
 
   describe('@is[num] comment', () => {
     it("should generate model's schema type without comment name specify", () => {
diff --git a/tests/unit/swagger/schemaDetails3.spec.ts b/tests/unit/swagger/schemaDetails3.spec.ts
index 2215f33d..deb477b0 100644
--- a/tests/unit/swagger/schemaDetails3.spec.ts
+++ b/tests/unit/swagger/schemaDetails3.spec.ts
@@ -81,6 +81,18 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
     });
   });
 
+  describe('info', () => {
+    it('should generate a valid info object', () => {
+      expect(specDefault.spec.info).to.deep.equal({
+        title: 'Test API',
+        description: 'Description of a test API',
+        contact: { email: 'jane@doe.com', name: 'Jane Doe', url: 'www.jane-doe.com' },
+        license: { name: 'MIT' },
+        version: '1.0.0',
+      });
+    });
+  });
+
   describe('security', () => {
     it('should replace the parent securityDefinitions with securitySchemes within components', () => {
       expect(specDefault.spec).to.not.have.property('securityDefinitions');