Skip to content

Commit 36432f0

Browse files
authoredOct 7, 2019
Merge pull request lukeautry#498 from ryankeener/hidden-controller
Add support for using @hidden() decorator on controllers
2 parents 0c53462 + 8fc9c3a commit 36432f0

File tree

7 files changed

+123
-6
lines changed

7 files changed

+123
-6
lines changed
 

‎README.MD

+29
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,35 @@ public async find(): Promise<any> {
800800
}
801801
```
802802

803+
### Hidden
804+
805+
Excludes this endpoint from the generated swagger document.
806+
807+
```ts
808+
@Get()
809+
@Hidden()
810+
public async find(): Promise<any> {
811+
812+
}
813+
```
814+
815+
It can also be set at the controller level to exclude all of its endpoints from the swagger document.
816+
817+
```ts
818+
@Hidden()
819+
export class HiddenController {
820+
@Get()
821+
public async find(): Promise<any> {
822+
823+
}
824+
825+
@Post()
826+
public async create(): Promise<any> {
827+
828+
}
829+
}
830+
```
831+
803832
## Command Line Interface
804833

805834
For information on the configuration object (tsoa.json), check out the following:

‎src/metadataGeneration/controllerGenerator.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ export class ControllerGenerator {
1010
private readonly path?: string;
1111
private readonly tags?: string[];
1212
private readonly security?: Tsoa.Security[];
13+
private readonly isHidden?: boolean;
1314

1415
constructor(private readonly node: ts.ClassDeclaration, private readonly current: MetadataGenerator) {
1516
this.path = this.getPath();
1617
this.tags = this.getTags();
1718
this.security = this.getSecurity();
19+
this.isHidden = this.getIsHidden();
1820
}
1921

2022
public IsValid() {
@@ -42,7 +44,7 @@ export class ControllerGenerator {
4244
private buildMethods() {
4345
return this.node.members
4446
.filter(m => m.kind === ts.SyntaxKind.MethodDeclaration)
45-
.map((m: ts.MethodDeclaration) => new MethodGenerator(m, this.current, this.tags, this.security))
47+
.map((m: ts.MethodDeclaration) => new MethodGenerator(m, this.current, this.tags, this.security, this.isHidden))
4648
.filter(generator => generator.IsValid())
4749
.map(generator => generator.Generate());
4850
}
@@ -85,4 +87,16 @@ export class ControllerGenerator {
8587

8688
return getSecurities(securityDecorators);
8789
}
90+
91+
private getIsHidden(): boolean {
92+
const hiddenDecorators = getDecorators(this.node, identifier => identifier.text === 'Hidden');
93+
if (!hiddenDecorators || !hiddenDecorators.length) {
94+
return false;
95+
}
96+
if (hiddenDecorators.length > 1) {
97+
throw new GenerateMetadataError(`Only one Hidden decorator allowed in '${this.node.name!.text}' class.`);
98+
}
99+
100+
return true;
101+
}
88102
}

‎src/metadataGeneration/methodGenerator.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ export class MethodGenerator {
1313
private method: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head';
1414
private path: string;
1515

16-
constructor(private readonly node: ts.MethodDeclaration, private readonly current: MetadataGenerator, private readonly parentTags?: string[], private readonly parentSecurity?: Tsoa.Security[]) {
16+
constructor(
17+
private readonly node: ts.MethodDeclaration,
18+
private readonly current: MetadataGenerator,
19+
private readonly parentTags?: string[],
20+
private readonly parentSecurity?: Tsoa.Security[],
21+
private readonly isParentHidden?: boolean,
22+
) {
1723
this.processMethodDecorators();
1824
}
1925

@@ -251,8 +257,13 @@ export class MethodGenerator {
251257
private getIsHidden() {
252258
const hiddenDecorators = this.getDecoratorsByIdentifier(this.node, 'Hidden');
253259
if (!hiddenDecorators || !hiddenDecorators.length) {
254-
return false;
260+
return !!this.isParentHidden;
261+
}
262+
263+
if (this.isParentHidden) {
264+
throw new GenerateMetadataError(`Hidden decorator cannot be set on '${this.getCurrentLocation()}' it is already defined on the controller`);
255265
}
266+
256267
if (hiddenDecorators.length > 1) {
257268
throw new GenerateMetadataError(`Only one Hidden decorator allowed in '${this.getCurrentLocation}' method.`);
258269
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Controller, Get, Hidden, Post, Route } from '../../../src';
2+
import { ModelService } from '../services/modelService';
3+
import { TestModel } from '../testModel';
4+
5+
@Route('Controller')
6+
@Hidden()
7+
export class HiddenMethodController extends Controller {
8+
@Get('hiddenGetMethod')
9+
public async hiddenGetMethod(): Promise<TestModel> {
10+
return Promise.resolve(new ModelService().getModel());
11+
}
12+
@Post('hiddenPostMethod')
13+
public async hiddenPostMethod(): Promise<TestModel> {
14+
return Promise.resolve(new ModelService().getModel());
15+
}
16+
}

‎tests/unit/swagger/definitionsGeneration/metadata.spec.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ describe('Metadata generation', () => {
480480
const parameterMetadata = new MetadataGenerator('./tests/fixtures/controllers/hiddenMethodController.ts').Generate();
481481
const controller = parameterMetadata.controllers[0];
482482

483-
it('should generate methods visible by default', () => {
483+
it('should mark methods as visible by default', () => {
484484
const method = controller.methods.find(m => m.name === 'normalGetMethod');
485485
if (!method) {
486486
throw new Error('Method normalGetMethod not defined!');
@@ -491,7 +491,7 @@ describe('Metadata generation', () => {
491491
expect(method.isHidden).to.equal(false);
492492
});
493493

494-
it('should generate hidden methods', () => {
494+
it('should mark methods as hidden', () => {
495495
const method = controller.methods.find(m => m.name === 'hiddenGetMethod');
496496
if (!method) {
497497
throw new Error('Method hiddenGetMethod not defined!');
@@ -503,6 +503,18 @@ describe('Metadata generation', () => {
503503
});
504504
});
505505

506+
describe('HiddenControllerGenerator', () => {
507+
const parameterMetadata = new MetadataGenerator('./tests/fixtures/controllers/hiddenController.ts').Generate();
508+
const controller = parameterMetadata.controllers[0];
509+
510+
it('should mark all methods as hidden', () => {
511+
expect(controller.methods).to.have.lengthOf(2);
512+
controller.methods.forEach(method => {
513+
expect(method.isHidden).to.equal(true);
514+
});
515+
});
516+
});
517+
506518
describe('DeprecatedMethodGenerator', () => {
507519
const parameterMetadata = new MetadataGenerator('./tests/fixtures/controllers/deprecatedController.ts').Generate();
508520
const controller = parameterMetadata.controllers[0];

‎tests/unit/swagger/schemaDetails.spec.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { expect } from 'chai';
21
import 'mocha';
2+
3+
import { expect } from 'chai';
4+
35
import { MetadataGenerator } from '../../../src/metadataGeneration/metadataGenerator';
46
import { SpecGenerator2 } from '../../../src/swagger/specGenerator2';
57
import { getDefaultOptions } from '../../fixtures/defaultOptions';
@@ -52,4 +54,22 @@ describe('Schema details generation', () => {
5254
}
5355

5456
it('should set API license if provided', () => expect(licenseName).to.equal(getDefaultOptions().license));
57+
58+
describe('paths', () => {
59+
describe('hidden paths', () => {
60+
it('should not contain hidden paths', () => {
61+
const metadataHiddenMethod = new MetadataGenerator('./tests/fixtures/controllers/hiddenMethodController.ts').Generate();
62+
const specHiddenMethod = new SpecGenerator2(metadataHiddenMethod, getDefaultOptions()).GetSpec();
63+
64+
expect(specHiddenMethod.paths).to.have.keys(['/Controller/normalGetMethod']);
65+
});
66+
67+
it('should not contain paths for hidden controller', () => {
68+
const metadataHiddenController = new MetadataGenerator('./tests/fixtures/controllers/hiddenController.ts').Generate();
69+
const specHiddenController = new SpecGenerator2(metadataHiddenController, getDefaultOptions()).GetSpec();
70+
71+
expect(specHiddenController.paths).to.be.empty;
72+
});
73+
});
74+
});
5575
});

‎tests/unit/swagger/schemaDetails3.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,21 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
130130
});
131131
});
132132
});
133+
describe('hidden paths', () => {
134+
it('should not contain hidden paths', () => {
135+
const metadataHiddenMethod = new MetadataGenerator('./tests/fixtures/controllers/hiddenMethodController.ts').Generate();
136+
const specHiddenMethod = new SpecGenerator3(metadataHiddenMethod, getDefaultOptions()).GetSpec();
137+
138+
expect(specHiddenMethod.paths).to.have.keys(['/Controller/normalGetMethod']);
139+
});
140+
141+
it('should not contain paths for hidden controller', () => {
142+
const metadataHiddenController = new MetadataGenerator('./tests/fixtures/controllers/hiddenController.ts').Generate();
143+
const specHiddenController = new SpecGenerator3(metadataHiddenController, getDefaultOptions()).GetSpec();
144+
145+
expect(specHiddenController.paths).to.be.empty;
146+
});
147+
});
133148
});
134149

135150
describe('components', () => {

0 commit comments

Comments
 (0)
Please sign in to comment.