Skip to content
Draft
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
219 changes: 219 additions & 0 deletions src/decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { NoTransformConfigurationError } from "./transformers/NoTransformConfigurationError";

import { IValidation } from "./IValidation";
import { TypeGuardError } from "./TypeGuardError";

/* ===========================================================
DECORATORS
- ASSERT
- IS
- VALIDATE
==============================================================
ASSERT
----------------------------------------------------------- */
/**
* Asserts a method with its parameters.
*
* Asserts a method, by wrapping the method and checking its parameters through
* {@link assert} function. If some parameter does not match the expected type, it
* throws an {@link TypeGuardError} or a custom error generated by the *errorFactory*
* parameter.
*
* For reference, {@link TypeGuardError.path} would be a little bit different with
* individual {@link assert} function. If the {@link TypeGuardError} occurs from
* some parameter, the path would start from `$input.parameters[number]`.
*
* This decorator is equivalent to using {@link functional.assertParameters} but
* works as a TypeScript method decorator for class methods.
*
* @param errorFactory Custom error factory. Default is `TypeGuardError`
* @returns Method decorator
* @throws A {@link TypeGuardError} or a custom error generated by *errorFactory*
*
* @author Jeongho Nam - https://github.com/samchon
*/
export function assert(
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
): MethodDecorator;

/**
* @internal
*/
export function assert(): never {
NoTransformConfigurationError("decorators.assert");
}

/**
* Asserts a method with strict equality of its parameters.
*
* Asserts a method, by wrapping the method and checking its parameters through
* {@link assertEquals} function. If some parameter does not match the expected type,
* it throws an {@link TypeGuardError} or a custom error generated by the *errorFactory*
* parameter.
*
* For reference, {@link TypeGuardError.path} would be a little bit different with
* individual {@link assertEquals} function. If the {@link TypeGuardError} occurs from
* some parameter, the path would start from `$input.parameters[number]`.
*
* This decorator is equivalent to using {@link functional.assertEqualsParameters} but
* works as a TypeScript method decorator for class methods.
*
* On the other hand, if you want to allow superfluous properties that are not enrolled
* to the parameter types, you can use {@link assert} decorator instead.
*
* @param errorFactory Custom error factory. Default is `TypeGuardError`
* @returns Method decorator
* @throws A {@link TypeGuardError} or a custom error generated by *errorFactory*
*
* @author Jeongho Nam - https://github.com/samchon
*/
export function assertEquals(
errorFactory?: undefined | ((props: TypeGuardError.IProps) => Error),
): MethodDecorator;

/**
* @internal
*/
export function assertEquals(): never {
NoTransformConfigurationError("decorators.assertEquals");
}

/* -----------------------------------------------------------
IS
----------------------------------------------------------- */
/**
* Tests a method's parameters.
*
* Tests a method, by wrapping the method and checking its parameters through
* {@link is} function. If some parameter does not match the expected type, it
* returns `null`. Otherwise there's no type error, it returns the result of the
* method.
*
* This decorator is equivalent to using {@link functional.isParameters} but
* works as a TypeScript method decorator for class methods.
*
* By the way, if you want is not just testing type checking, but also finding
* detailed type error reason(s), then use {@link assert} or {@link validate}
* decorators instead.
*
* On the other hand, if you don't want to allow any superfluous properties,
* utilize {@link equals} decorator instead.
*
* @returns Method decorator
*
* @author Jeongho Nam - https://github.com/samchon
*/
export function is(): MethodDecorator;

/**
* @internal
*/
export function is(): never {
NoTransformConfigurationError("decorators.is");
}

/**
* Tests a method's parameters with strict equality.
*
* Tests a method, by wrapping the method and checking its parameters through
* {@link equals} function. If some parameter does not match the expected type, it
* returns `null`. Otherwise there's no type error, it returns the result of the
* method.
*
* This decorator is equivalent to using {@link functional.equalsParameters} but
* works as a TypeScript method decorator for class methods.
*
* By the way, if you want is not just testing type checking, but also finding
* detailed type error reason(s), then use {@link assertEquals} or {@link validateEquals}
* decorators instead.
*
* On the other hand, if you want to allow superfluous properties that are not enrolled
* to the parameter types, you can use {@link is} decorator instead.
*
* @returns Method decorator
*
* @author Jeongho Nam - https://github.com/samchon
*/
export function equals(): MethodDecorator;

/**
* @internal
*/
export function equals(): never {
NoTransformConfigurationError("decorators.equals");
}

/* -----------------------------------------------------------
VALIDATE
----------------------------------------------------------- */
/**
* Validates a method's parameters.
*
* Validates a method, by wrapping the method and checking its parameters through
* {@link validate} function. If some parameter does not match the expected type, it
* returns {@link IValidation.IError} typed object. Otherwise there's no type error, it
* returns {@link IValidation.ISuccess} typed object instead.
*
* For reference, {@link IValidation.IError.path} would be a little bit different with
* individual {@link validate} function. If the {@link IValidation.IError} occurs from
* some parameter, the path would start from `$input.parameters[number]`.
*
* This decorator is equivalent to using {@link functional.validateParameters} but
* works as a TypeScript method decorator for class methods.
*
* By the way, if what you want is not finding every type errors, but just finding
* the 1st type error, then use {@link assert} decorator instead. Otherwise, if you
* just want to know whether the parameters are matched with their types, {@link is}
* decorator is the way to go.
*
* On the other hand, if you don't want to allow any superfluous properties, utilize
* {@link validateEquals} decorator instead.
*
* @returns Method decorator
*
* @author Jeongho Nam - https://github.com/samchon
*/
export function validate(): MethodDecorator;

/**
* @internal
*/
export function validate(): never {
NoTransformConfigurationError("decorators.validate");
}

/**
* Validates a method's parameters with strict equality.
*
* Validates a method, by wrapping the method and checking its parameters through
* {@link validateEquals} function. If some parameter does not match the expected type, it
* returns {@link IValidation.IError} typed object. Otherwise there's no type error, it
* returns {@link IValidation.ISuccess} typed object instead.
*
* For reference, {@link IValidation.IError.path} would be a little bit different with
* individual {@link validateEquals} function. If the {@link IValidation.IError} occurs from
* some parameter, the path would start from `$input.parameters[number]`.
*
* This decorator is equivalent to using {@link functional.validateEqualsParameters} but
* works as a TypeScript method decorator for class methods.
*
* By the way, if what you want is not finding every type errors, but just finding
* the 1st type error, then use {@link assertEquals} decorator instead. Otherwise, if you
* just want to know whether the parameters are matched with their types, {@link equals}
* decorator is the way to go.
*
* On the other hand, if you want to allow superfluous properties that are not enrolled
* to the parameter types, you can use {@link validate} decorator instead.
*
* @returns Method decorator
*
* @author Jeongho Nam - https://github.com/samchon
*/
export function validateEquals(): MethodDecorator;

/**
* @internal
*/
export function validateEquals(): never {
NoTransformConfigurationError("decorators.validateEquals");
}
1 change: 1 addition & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { IValidation } from "./IValidation";
import { Resolved } from "./Resolved";
import { TypeGuardError } from "./TypeGuardError";

export * as decorators from "./decorators";
export * as functional from "./functional";
export * as http from "./http";
export * as llm from "./llm";
Expand Down
50 changes: 50 additions & 0 deletions src/programmers/decorators/DecoratorAssertParametersProgrammer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import ts from "typescript";

import { FunctionalAssertParametersProgrammer } from "../functional/FunctionalAssertParametersProgrammer";

import { ITypiaContext } from "../../transformers/ITypiaContext";

export namespace DecoratorAssertParametersProgrammer {
export interface IConfig {
equals: boolean;
}

export interface IProps {
context: ITypiaContext;
modulo: ts.LeftHandSideExpression;
config: IConfig;
method: ts.MethodDeclaration;
expression: ts.Expression;
init?: ts.Expression | undefined;
}

export const write = (props: IProps): ts.Expression => {
// Convert the method to a function declaration for the functional programmer
const functionDeclaration = createFunctionDeclarationFromMethod(props.method);

// Reuse the functional programmer logic
return FunctionalAssertParametersProgrammer.write({
context: props.context,
modulo: props.modulo,
config: props.config,
declaration: functionDeclaration,
expression: props.expression,
init: props.init,
});
};

const createFunctionDeclarationFromMethod = (
method: ts.MethodDeclaration,
): ts.FunctionDeclaration => {
// Create a synthetic function declaration that matches the method signature
return ts.factory.createFunctionDeclaration(
method.modifiers?.filter(ts.isModifier),
method.asteriskToken,
ts.factory.createIdentifier("__method"),
method.typeParameters,
method.parameters,
method.type,
method.body || ts.factory.createBlock([]),
);
};
}
53 changes: 53 additions & 0 deletions src/transformers/CallExpressionTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { FunctionalValidateParametersProgrammer } from "../programmers/functiona
import { FunctionalValidateReturnProgrammer } from "../programmers/functional/FunctionalValidateReturnProgrammer";
import { FunctionalGenericTransformer } from "./features/functional/FunctionalGenericTransformer";

import { DecoratorTransformer } from "./features/decorators/DecoratorTransformer";

import { NamingConvention } from "../utils/NamingConvention";

import { ITransformProps } from "./ITransformProps";
Expand Down Expand Up @@ -551,4 +553,55 @@ const FUNCTORS: Record<string, Record<string, () => Task>> = {
NamingConvention.snake,
),
},
decorators: {
// ASSERTIONS
assert: () =>
DecoratorTransformer.transform({
method: "assert",
config: {
equals: false,
},
programmer: () => ts.factory.createIdentifier("undefined"), // placeholder
}),
assertEquals: () =>
DecoratorTransformer.transform({
method: "assertEquals",
config: {
equals: true,
},
programmer: () => ts.factory.createIdentifier("undefined"), // placeholder
}),
is: () =>
DecoratorTransformer.transform({
method: "is",
config: {
equals: false,
},
programmer: () => ts.factory.createIdentifier("undefined"), // placeholder
}),
equals: () =>
DecoratorTransformer.transform({
method: "equals",
config: {
equals: true,
},
programmer: () => ts.factory.createIdentifier("undefined"), // placeholder
}),
validate: () =>
DecoratorTransformer.transform({
method: "validate",
config: {
equals: false,
},
programmer: () => ts.factory.createIdentifier("undefined"), // placeholder
}),
validateEquals: () =>
DecoratorTransformer.transform({
method: "validateEquals",
config: {
equals: true,
},
programmer: () => ts.factory.createIdentifier("undefined"), // placeholder
}),
},
};
Loading