Skip to content

Commit

Permalink
ObjectTypeLiteral support
Browse files Browse the repository at this point in the history
  • Loading branch information
WoH committed Aug 23, 2019
1 parent 3bd067b commit 6d21daf
Show file tree
Hide file tree
Showing 15 changed files with 140 additions and 8 deletions.
8 changes: 7 additions & 1 deletion src/metadataGeneration/tsoa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,13 @@ export namespace Tsoa {
| 'any'
| 'refEnum'
| 'refObject'
| 'objectLiteral'
| 'union'
| 'intersection';

export type RefTypeLiteral = 'refObject' | 'refEnum';

export type PrimitiveTypeLiteral = Exclude<TypeStringLiteral, RefTypeLiteral | 'enum' | 'array' | 'void' | 'union' | 'intersection'>;
export type PrimitiveTypeLiteral = Exclude<TypeStringLiteral, RefTypeLiteral | 'enum' | 'array' | 'void' | 'objectLiteral' | 'union' | 'intersection'>;

export interface Type {
dataType: TypeStringLiteral;
Expand All @@ -107,6 +108,11 @@ export namespace Tsoa {
enums: string[];
}

export interface ObjectLiteralType extends Type {
dataType: 'objectLiteral';
properties: Property[];
}

export interface ArrayType extends Type {
dataType: 'array';
elementType: Type;
Expand Down
17 changes: 16 additions & 1 deletion src/metadataGeneration/typeResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,22 @@ export class TypeResolver {
}

if (this.typeNode.kind === ts.SyntaxKind.TypeLiteral) {
return { dataType: 'any' } as Tsoa.Type;
const properties = (this.typeNode as ts.TypeLiteralNode).members
.filter(member => member.kind === ts.SyntaxKind.PropertySignature)
.reduce((res, propertySignature: ts.PropertySignature) => {
const type = new TypeResolver(propertySignature.type as ts.TypeNode, this.current, this.typeNode).resolve();

return [
{
name: (propertySignature.name as ts.Identifier).text,
required: !propertySignature.questionToken,
type,
} as Tsoa.Property,
...res,
];
}, []);

return { dataType: 'objectLiteral', properties } as Tsoa.ObjectLiteralType;
}

if (this.typeNode.kind === ts.SyntaxKind.ObjectKeyword) {
Expand Down
20 changes: 20 additions & 0 deletions src/swagger/specGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export abstract class SpecGenerator {
return this.getSwaggerTypeForUnionType(type as Tsoa.UnionType);
} else if (type.dataType === 'intersection') {
return this.getSwaggerTypeForIntersectionType(type as Tsoa.IntersectionType);
} else if (type.dataType === 'objectLiteral') {
return this.getSwaggerTypeForObjectLiteral(type as Tsoa.ObjectLiteralType);
} else {
return assertNever(type.dataType);
}
Expand All @@ -87,6 +89,24 @@ export abstract class SpecGenerator {
protected abstract getSwaggerTypeForUnionType(type: Tsoa.UnionType);

protected abstract getSwaggerTypeForIntersectionType(type: Tsoa.IntersectionType);
public getSwaggerTypeForObjectLiteral(objectLiteral: Tsoa.ObjectLiteralType): Swagger.Schema {
const properties = objectLiteral.properties.reduce((acc, property: Tsoa.Property) => {
return {
[property.name]: this.getSwaggerType(property.type),
...acc,
};
}, {});

const required = objectLiteral.properties.filter(prop => prop.required).map(prop => prop.name);

// An empty list required: [] is not valid.
// If all properties are optional, do not specify the required keyword.
return {
properties,
...(required && required.length && { required }),
type: 'object',
};
}

protected getSwaggerTypeForReferenceType(referenceType: Tsoa.ReferenceType): Swagger.BaseSchema {
return {
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/controllers/getController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export class GetTestController extends Controller {
modelsArray: new Array<TestSubModel>(),
numberArray: [1, 2, 3],
numberValue: 1,
objLiteral: {
name: 'a string',
},
object: {
a: 'a',
},
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/inversify/managedService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export class ManagedService {
modelsArray: new Array<TestSubModel>(),
numberArray: [1, 2, 3],
numberValue: 1,
objLiteral: {
name: 'hello',
},
object: {
a: 'a',
},
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/services/modelService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export class ModelService {
modelsArray: new Array<TestSubModel>(),
numberArray: [1, 2, 3],
numberValue: 1,
objLiteral: {
name: 'hello',
},
object: {
a: 'a',
},
Expand Down
18 changes: 12 additions & 6 deletions tests/fixtures/testModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ export interface TestModel extends Model {
genericNestedArrayKeyword2?: GenericRequest<Array<TypeAliasModel2>>;
genericNestedArrayCharacter2?: GenericRequest<TypeAliasModel2[]>;
mixedUnion?: string | TypeAliasModel1;

objLiteral: {
name: string;
nested?: {
bool: boolean;
optional?: number;
allOptional: {
one?: string;
two?: string;
};
};
};
}

export interface TypeAliasModel1 {
Expand Down Expand Up @@ -333,12 +345,6 @@ export class TestClassModel extends TestClassBaseModel {
stringProperty: string;
protected protectedStringProperty: string;

public static typeLiterals = {
booleanTypeLiteral: { $type: Boolean },
numberTypeLiteral: { $type: Number },
stringTypeLiteral: { $type: String },
};

/**
* @param publicConstructorVar This is a description for publicConstructorVar
*/
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/dynamic-controllers-express-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,9 @@ describe('Express Server', () => {
},
numberArray: [1, 2],
numberValue: 5,
objLiteral: {
name: 'hello',
},
object: { foo: 'bar' },
objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
optionalString: 'test1234',
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/express-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,9 @@ describe('Express Server', () => {
},
numberArray: [1, 2],
numberValue: 5,
objLiteral: {
name: 'hello',
},
object: { foo: 'bar' },
objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
optionalString: 'test1234',
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/hapi-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,9 @@ describe('Hapi Server', () => {
},
numberArray: [1, 2],
numberValue: 5,
objLiteral: {
name: 'hello',
},
object: { foo: 'bar' },
objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
optionalString: 'test1234',
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/inversify-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ describe('Inversify Express Server', () => {
},
numberArray: [1, 2, 3],
numberValue: 1,
objLiteral: {
name: 'hello',
},
object: {
a: 'a',
},
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/koa-server-no-additional-allowed.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ describe('Koa Server (with noImplicitAdditionalProperties turned on)', () => {
modelsArray: [{ email: '[email protected]', id: 1 }],
numberArray: [1, 2],
numberValue: 5,
objLiteral: {
name: 'hello',
},
object: { foo: 'bar' },
objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
optionalString: 'test1234',
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/koa-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,9 @@ describe('Koa Server', () => {
modelsArray: [{ email: '[email protected]', id: 1 }],
numberArray: [1, 2],
numberValue: 5,
objLiteral: {
name: 'hello',
},
object: { foo: 'bar' },
objectArray: [{ foo1: 'bar1' }, { foo2: 'bar2' }],
optionalString: 'test1234',
Expand Down
28 changes: 28 additions & 0 deletions tests/unit/swagger/definitionsGeneration/definitions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,29 @@ describe('Definition generation', () => {
expect(propertySchema.type).to.eq('object', `for property ${propertyName}`);
expect(propertySchema).to.not.haveOwnProperty('additionalProperties', `for property ${propertyName}`);
},
objLiteral: (propertyName, propertySchema) => {
expect(propertySchema).to.deep.include({
properties: {
name: {
type: 'string',
},
nested: {
properties: {
allOptional: {
properties: { one: { type: 'string' }, two: { type: 'string' } },
type: 'object',
},
bool: { type: 'boolean' },
optional: { format: 'double', type: 'number' },
},
required: ['allOptional', 'bool'],
type: 'object',
},
},
required: ['name'],
type: 'object',
});
},
};

Object.keys(assertionsPerProperty).forEach(aPropertyName => {
Expand All @@ -438,6 +461,11 @@ describe('Definition generation', () => {
Object.keys(definition.properties!).length,
`because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`,
);

expect(Object.keys(assertionsPerProperty)).to.length(
Object.keys(definition.properties!).length,
`because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`,
);
});
});

Expand Down
30 changes: 30 additions & 0 deletions tests/unit/swagger/schemaDetails3.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,29 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
expect(propertySchema.description).to.eq(undefined, `for property ${propertyName}.description`);
expect(propertySchema).to.not.haveOwnProperty('additionalProperties', `for property ${propertyName}`);
},
objLiteral: (propertyName, propertySchema) => {
expect(propertySchema).to.deep.include({
properties: {
name: {
type: 'string',
},
nested: {
properties: {
allOptional: {
properties: { one: { type: 'string' }, two: { type: 'string' } },
type: 'object',
},
bool: { type: 'boolean' },
optional: { format: 'double', type: 'number' },
},
required: ['allOptional', 'bool'],
type: 'object',
},
},
required: ['name'],
type: 'object',
});
},
object: (propertyName, propertySchema) => {
expect(propertySchema.type).to.eq('object', `for property ${propertyName}`);
if (currentSpec.specName === 'specWithNoImplicitExtras') {
Expand Down Expand Up @@ -481,6 +504,13 @@ describe('Definition generation for OpenAPI 3.0.0', () => {
`because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`,
);
});

it('should have only created schemas for properties on the TypeScript interface', () => {
expect(Object.keys(assertionsPerProperty)).to.length(
Object.keys(testModel.properties!).length,
`because the swagger spec (${currentSpec.specName}) should only produce property schemas for properties that live on the TypeScript interface.`,
);
});
});
});
});
Expand Down

0 comments on commit 6d21daf

Please sign in to comment.