Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 86 additions & 1 deletion packages/type/src/reflection/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* You should have received a copy of the MIT License along with this program.
*/

import { AbstractClassType, arrayRemoveItem, ClassType, getClassName, getInheritanceChain, getParentClass, indent, isArray, isClass, isGlobalClass, TypeAnnotation } from '@deepkit/core';
import { AbstractClassType, arrayRemoveItem, capitalize, ClassType, getClassName, getInheritanceChain, getParentClass, indent, isArray, isClass, isGlobalClass, TypeAnnotation } from '@deepkit/core';
import { TypeNumberBrand } from '@deepkit/type-spec';
import { getProperty, ReceiveType, reflect, ReflectionClass, resolveReceiveType, toSignature } from './reflection.js';
import { isExtendable } from './extends.js';
Expand Down Expand Up @@ -2208,6 +2208,66 @@ export const typeDecorators: TypeDecorator[] = [
},
];

export function stringifyAnnotationAsType(symbol: Symbol, type: Type): string | [string, Type[]][] {
switch (symbol) {
case primaryKeyAnnotation.symbol:
return 'PrimaryKey';
case autoIncrementAnnotation.symbol:
return 'AutoIncrement';
case uuidAnnotation.symbol:
return 'UUID';
case mongoIdAnnotation.symbol:
return 'MongoId';
case binaryBigIntAnnotation.symbol: {
return binaryBigIntAnnotation.getFirst(type) === BinaryBigIntType.signed ? 'SignedBinaryBigInt' : 'BinaryBigInt';
}
case referenceAnnotation.symbol:
return 'Reference';
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and a few others should return ['Reference,' args]

case backReferenceAnnotation.symbol:
return 'BackReference';
case embeddedAnnotation.symbol:
return 'Embedded';
case groupAnnotation.symbol:
return 'Group';
case excludedAnnotation.symbol:
return 'Excluded';
case indexAnnotation.symbol:
return 'Index';
case databaseAnnotation.symbol:
return 'DatabaseField';
case validationAnnotation.symbol: {
const validations = validationAnnotation.getAnnotations(type);
const defaultArgsMapping: Record<string, any[]> = {
'decimal': [1, 100],
// todo: implement all defaults, or find a better way
};
return validations.map(v => {
const defaults = defaultArgsMapping[v.name];
const argValues = v.args.map(v => typeToObject(v));
let args = v.args;
if (defaults) {
args = v.args.filter((arg, index) => {
const def = defaults[index];
const argValue = argValues[index];
return argValue !== def;
});
}

switch (v.name) {
case 'positive': {
return [argValues[0] === true ? 'Positive' : 'PositiveNoZero', []];
}
// todo implement more normalization, see validator.ts
}

return [v.name, args];
});
}
default:
return '';
}
}

export function typeToObject(type?: Type, state: { stack: Type[] } = { stack: [] }): any {
if (!type) return;

Expand Down Expand Up @@ -2464,6 +2524,31 @@ export function stringifyType(type: Type, stateIn: Partial<StringifyTypeOptions>
continue;
}

if (type.annotations) {
for (const symbol of Object.getOwnPropertySymbols(type.annotations)) {
const annotationString = stringifyAnnotationAsType(symbol, type);
if ('string' === typeof annotationString && annotationString) {
stack.push({ before: annotationString });
stack.push({ before: ' & ' });
} else if (isArray(annotationString)) {
for (let i = annotationString.length - 1; i >= 0; i--) {
const [name, args] = annotationString[i];
if (args && args.length) {
stack.push({ after: '>' });
for (let j = args.length - 1; j >= 0; j--) {
stack.push({ type: args[j] });
if (j !== 0) {
stack.push({ after: ', ' });
}
}
stack.push({ after: '<' });
}
stack.push({ after: ' & ' + capitalize(name) });
}
}
}
}

switch (type.kind) {
case ReflectionKind.never:
result.push(`never`);
Expand Down
31 changes: 28 additions & 3 deletions packages/type/tests/stringify.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { expect, test } from '@jest/globals';
import { stringifyResolvedType, stringifyShortResolvedType, stringifyType, Type } from '../src/reflection/type.js';
import { PrimaryKey, stringifyResolvedType, stringifyShortResolvedType, stringifyType, Type } from '../src/reflection/type.js';
import { reflect, typeOf } from '../src/reflection/reflection.js';
import { deserializeType, serializeType } from '../src/type-serialization.js';
import { Decimal, MaxLength, MinLength, Positive, PositiveNoZero } from '../src/validator.js';

test('stringifyType basic', () => {
expect(stringifyResolvedType(typeOf<string>())).toBe('string');
Expand Down Expand Up @@ -309,7 +310,31 @@ test('stringifyType object literal inline', () => {

test('stringifyType type', () => {
const type = typeOf<Type>();
const s = stringifyType(type, {showFullDefinition: true});
const s = stringifyType(type, { showFullDefinition: true });
});

test('serialized stringifyType contains annotations', () => {
type A = string & PrimaryKey;
const serialized = serializeType(typeOf<A>());
const deserialized = deserializeType(serialized);
expect(stringifyResolvedType(deserialized)).toBe(`string & PrimaryKey`);
});

test('stringifyType contains annotations with Parameter 1', () => {
type A = string & MinLength<5> & MaxLength<15>;
expect(stringifyResolvedType(typeOf<A>())).toBe(`string & MinLength<5> & MaxLength<15>`);
});

test('stringifyType contains annotations with Parameter 2', () => {
type A = string & Decimal<5> & Positive;
expect(stringifyResolvedType(typeOf<A>())).toBe(`string & Decimal<5> & Positive`);
});

test('stringifyType contains annotations with Parameter 3', () => {
type A1 = string & PositiveNoZero;
expect(stringifyResolvedType(typeOf<A1>())).toBe(`string & PositiveNoZero`);
type A2 = string & Positive;
expect(stringifyResolvedType(typeOf<A2>())).toBe(`string & Positive`);
});

test('generic', () => {
Expand All @@ -319,4 +344,4 @@ test('generic', () => {

expect(stringifyResolvedType(reflect(Gen))).toBe('Gen {id: T}');
expect(stringifyResolvedType(reflect(Gen, typeOf<number>()))).toBe('Gen {id: number}');
})
});
Loading