Skip to content

Commit

Permalink
Merge pull request #154 from samchon/v3.1
Browse files Browse the repository at this point in the history
V3.1, new function `validate()`
  • Loading branch information
samchon authored Jul 15, 2022
2 parents b87ff8e + 017e1b9 commit 9bd4848
Show file tree
Hide file tree
Showing 68 changed files with 1,898 additions and 70 deletions.
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@ Super-fast Runtime type checker and `JSON.stringify()` functions, with only one
```typescript
import TSON from "typescript-json";

//----
// MAIN FUNCTIONS
//----
TSON.assertType<T>(input); // runtime type checker throwing exception
TSON.is<T>(input); // runtime type checker returning boolean
// RUNTIME TYPE CHECKERS
TSON.assertType<T>(input); // throws exception
TSON.is<T>(input); // returns boolean value
TSON.validate<T>(input); // archives all type errors

// STRINGIFY
TSON.stringify<T>(input); // 5x faster JSON.stringify()

//----
// APPENDIX FUNCTIONS
//----
TSON.application<[T, U, V], "swagger">(); // JSON schema application generator
TSON.create<T>(input); // 2x faster object creator (only one-time construction)
```
Expand Down Expand Up @@ -131,11 +130,24 @@ module.exports = {
```typescript
export function assertType<T>(input: T): T;
export function is<T>(input: T): boolean;
export function validate<T>(input: T): IValidation;

export interface IValidation {
success: boolean;
errors: IValidation.IError[];
}
export namespace IValidation {
export interface IError {
path: string;
expected: string;
value: any;
}
}
```

`typescript-json` provides two runtime type checker functions, `assertType()` and `is()`.
`typescript-json` provides three runtime type checker functions.

The first, `assertType()` is a function throwing `TypeGuardError` when an `input` value is different with its type, generic argument `T`. The other function, `is()` returns a `boolean` value meaning whether matched or not.
The first, `assertType()` is a function throwing `TypeGuardError` when an `input` value is different with its type, generic argument `T`. The second function, `is()` returns a `boolean` value meaning whether matched or not. The last `validate()` function archives all type errors into an `IValidation.errors` array.

Comparing those type checker functions with other similar libraries, `typescript-json` is much easier than others, except only `typescript-is`. For example, `ajv` requires complicate JSON schema definition that is different with the TypeScript type. Besides, `typescript-json` requires only one line.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typescript-json",
"version": "3.0.12",
"version": "3.1.1",
"description": "Runtime type checkers and 5x faster JSON.stringify() function",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
Expand Down
11 changes: 11 additions & 0 deletions src/IValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface IValidation {
success: boolean;
errors: IValidation.IError[];
}
export namespace IValidation {
export interface IError {
path: string;
expected: string;
value: any;
}
}
114 changes: 80 additions & 34 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ import { TypeGuardError } from "./TypeGuardError";
import { $number } from "./functional/$number";
import { $string } from "./functional/$string";
import { $tail } from "./functional/$last";
import { IValidation } from "./IValidation";

/* -----------------------------------------------------------
MAIN FUNCTIONS
VALIDATORS
----------------------------------------------------------- */
/**
* Asserts a value type in the runtime.
*
* Asserts a parametric value type and throws a {@link TypeGuardError} with detailed
* reason, if the parametric value is not following the type *T*. Otherwise, the
* value is following the type *T*, just input parameter would be returned.
* reason, if the parametric value is not following the type `T`. Otherwise, the
* value is following the type `T`, just input parameter would be returned.
*
* If what you want is not asserting but just knowing whether the parametric value is
* following the type *T* or not, you can choose the {@link is} function instead.
* following the type `T` or not, you can choose the {@link is} function instead.
* Otherwise you want to know all the errors, {@link validate} is the way to go.
*
* @template T Type of the input value
* @param input A value to be asserted
Expand Down Expand Up @@ -56,25 +58,22 @@ export namespace assertType {
}
}

// /**
// * @hidden
// */
// export interface assertType<T> {
// (input: T): T;
// }

/**
* Tests a value type in the runtime.
*
* Tests a parametric value type and returns whether it's following the type *T* or not.
* Tests a parametric value type and returns whether it's following the type `T` or not.
* If the parametric value is matched with the type `T`, `true` value would be returned.
* Otherwise, the parametric value is not following the type `T`, `false` value would be
* returned.
*
* If what you want is not just knowing whether the parametric value is following the
* type *T* or not, but throwing an exception with detailed reason, you can choose
* {@link is} function instead.
* type `T` or not, but throwing an exception with detailed reason, you can choose
* {@link is} function instead. Also, if you want to know all the errors with detailed
* reasons, {@link validate} function would be useful.
*
* @template T Type of the input value
* @param input A value to be tested
* @returns Whether the parametric value is following the type *T* or not
* @returns Whether the parametric value is following the type `T` or not
*
* @author Jeongho Nam - https://github.com/samchon
*/
Expand All @@ -87,19 +86,75 @@ export function is(): never {
halt("is");
}

// /**
// * @hidden
// */
// export interface is<T> {
// (input: T): boolean;
// }
/**
* Validate a value type in the runtime.
*
* Validates a parametric value type and archives all the type errors into an
* {@link IValidation.errors} array, if the parametric value is not following the
* type `T`. Of course, if the parametric value is following the type `T`, the
* {@link IValidation.errors} array would be empty and {@link IValidation.success}
* would have the `true` value.
*
* If what you want is not finding all the error, but asserting the parametric value
* type with exception throwing, you can choose {@link assertType} function instead.
* Otherwise, you just want to know whether the parametric value is matched with the
* type `T`, {@link is} function is the way to go.
*
* @template Type of the input value
* @param input A value to be validated
* @returns
*/
export function validate<T>(input: T): IValidation;

/**
* @internal
*/
export function validate(): never {
halt("validate");
}

/**
* @internal
*/
export namespace validate {
export const predicate =
(res: IValidation) =>
(
matched: boolean,
exceptionable: boolean,
closure: () => IValidation.IError,
) => {
// CHECK FAILURE
if (matched === false && exceptionable === true)
(() => {
res.success &&= false;

// TRACE ERROR
const error = closure();
if (res.errors.length) {
const last = res.errors[res.errors.length - 1]!.path;
if (
last.length >= error.path.length &&
last.substring(0, error.path.length) === error.path
)
return;
}
res.errors.push(error);
return;
})();
return matched;
};
}

/* -----------------------------------------------------------
STRINGIFY
----------------------------------------------------------- */
/**
* 5x faster `JSON.stringify()` function.
*
* Converts an input value to a JSON (JavaSript Object Noation) string, about 5x faster
* than the native `JSON.stringify()` function. The 5x faster principle is because
* it writes an optmized JSON conversion plan, only for the type *T*.
* it writes an optmized JSON conversion plan, only for the type `T`.
*
* If you want to create a stringify function which is reusable, just assign this function
* to a (constant) variable like below, with the generic argument `T`. Then the variable
Expand All @@ -113,7 +168,7 @@ export function is(): never {
* ```
*
* For reference, this `TSON.stringify()` does not validate the input value type. It
* just believes that the input value is following the type *T*. Therefore, if you
* just believes that the input value is following the type `T`. Therefore, if you
* can't ensure the input value type, it would better to call {@link assertType} or
* {@link is} function before.
*
Expand Down Expand Up @@ -150,17 +205,8 @@ export module stringify {
}
}

// export interface stringify<T> {
// /**
// * 5x faster `JSON.stringify()` function.
// *
// * A super-fast `JSON.stringify()` function generated by {@link stringify}.
// */
// (input: T): string;
// }

/* -----------------------------------------------------------
APPENDIX FUNCTION
APPENDIX FUNCTIONS
----------------------------------------------------------- */
/**
* 2x faster constant object creator.
Expand Down Expand Up @@ -192,7 +238,7 @@ export function create(): never {
}

/**
* > You must configure the generic argument *T*.
* > You must configure the generic argument `T`.
*
* JSON Schema Application.
*
Expand Down
6 changes: 4 additions & 2 deletions src/programmers/AssertProgrammer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export namespace AssertProgrammer {
unioners: "$au",
trace: true,
combiner: combine(),
joiner: CheckerProgrammer.DEFAULT_JOINER(),
},
() => [
StatementFactory.variable(
Expand Down Expand Up @@ -60,8 +61,9 @@ function combine(): CheckerProgrammer.IConfig.Combiner {
if (explore.tracable === false && explore.from !== "top")
return combiner(explore);

const path = explore.postfix ? `path + ${explore.postfix}` : "path";

const path: string = explore.postfix
? `path + ${explore.postfix}`
: "path";
return (logic) => (input, expressions, expected) =>
ts.factory.createCallExpression(
ts.factory.createIdentifier("$pred"),
Expand Down
56 changes: 35 additions & 21 deletions src/programmers/CheckerProgrammer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { ExpressionFactory } from "../factories/ExpressionFactory";
import { UnionExplorer } from "./helpers/UnionExplorer";
import { IProject } from "../transformers/IProject";
import { OptionPreditor } from "./helpers/OptionPredicator";
import { IExpressionEntry } from "./helpers/IExpressionEntry";

export namespace CheckerProgrammer {
export interface IConfig {
functors: string;
unioners: string;
trace: boolean;
combiner: IConfig.Combiner;
joiner: IConfig.IJoiner;
}
export namespace IConfig {
export interface Combiner {
Expand All @@ -30,6 +32,11 @@ export namespace CheckerProgrammer {
};
};
}
export interface IJoiner {
object(entries: IExpressionEntry[]): ts.Expression;
array(input: ts.Expression, arrow: ts.ArrowFunction): ts.Expression;
tuple(binaries: ts.Expression[]): ts.Expression;
}
}
export import IExplore = FeatureProgrammer.IExplore;

Expand All @@ -48,6 +55,23 @@ export namespace CheckerProgrammer {
export const generate_unioners = (project: IProject, config: IConfig) =>
FeatureProgrammer.generate_unioners(CONFIG(project, config));

export const DEFAULT_JOINER: () => IConfig.IJoiner = () => ({
object: (entries) =>
entries.length
? entries
.map((entry) => entry.expression)
.reduce((x, y) => ts.factory.createLogicalAnd(x, y))
: ts.factory.createTrue(),
array: (input, arrow) =>
ts.factory.createCallExpression(
IdentifierFactory.join(input, "every"),
undefined,
[arrow],
),
tuple: (binaries) =>
binaries.reduce((x, y) => ts.factory.createLogicalAnd(x, y)),
});

function CONFIG(
project: IProject,
config: IConfig,
Expand All @@ -73,14 +97,7 @@ export namespace CheckerProgrammer {
objector: {
checker: decode(project, config),
decoder: decode_object(config),
joiner: (entries) =>
entries.length
? entries
.map((entry) => entry.expression)
.reduce((x, y) =>
ts.factory.createLogicalAnd(x, y),
)
: ts.factory.createTrue(),
joiner: config.joiner.object,
unionizer: (input, targets, explore) =>
config.combiner(explore)("or")(
input,
Expand Down Expand Up @@ -225,10 +242,11 @@ export namespace CheckerProgrammer {
input,
[
...top,
config.combiner({
...explore,
// tracable: false,
})("or")(input, binaries, meta.getName()),
config.combiner(explore)("or")(
input,
binaries,
meta.getName(),
),
],
meta.getName(),
)
Expand Down Expand Up @@ -274,7 +292,7 @@ export namespace CheckerProgrammer {
tuple: Array<Metadata>,
explore: IExplore,
): ts.Expression {
const length = ts.factory.createStrictEquality(
const length: ts.BinaryExpression = ts.factory.createStrictEquality(
ts.factory.createPropertyAccessExpression(input, "length"),
ts.factory.createNumericLiteral(tuple.length),
);
Expand All @@ -293,8 +311,9 @@ export namespace CheckerProgrammer {
);
if (binaries.length === 0) return length;
else
return [length, ...binaries].reduce((x, y) =>
ts.factory.createLogicalAnd(x, y),
return ts.factory.createLogicalAnd(
length,
config.joiner.tuple(binaries),
);
};
}
Expand All @@ -305,12 +324,7 @@ export namespace CheckerProgrammer {
trace: config.trace,
decoder: decode(project, config),
},
(input, arrow) =>
ts.factory.createCallExpression(
IdentifierFactory.join(input, "every"),
undefined,
[arrow],
),
config.joiner.array,
);
}

Expand Down
1 change: 1 addition & 0 deletions src/programmers/IsProgrammer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export namespace IsProgrammer {
? expressions.reduce((x, y) => binder(x, y))
: initial;
},
joiner: CheckerProgrammer.DEFAULT_JOINER(),
};
}

Expand Down
Loading

0 comments on commit 9bd4848

Please sign in to comment.