Skip to content

Commit 4102ed9

Browse files
committed
Initial commit
0 parents  commit 4102ed9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+4446
-0
lines changed

.github/workflows/ci.yml

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Node CI
2+
3+
on: [push,pull_request]
4+
jobs:
5+
build:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v2
9+
- uses: actions/setup-node@v1
10+
with:
11+
node-version: 10
12+
- name: Install yarn
13+
run: npm install -g yarn
14+
- run: yarn install
15+
- run: yarn build
16+
- name: Add artifact to github
17+
uses: actions/upload-artifact@v2
18+
with:
19+
name: artifact
20+
path: dist
21+
22+
test:
23+
needs: [build]
24+
strategy:
25+
matrix:
26+
os:
27+
- macOS-latest
28+
- ubuntu-latest
29+
- windows-latest
30+
node-version:
31+
- 10.x
32+
- 12.x
33+
runs-on: ${{ matrix.os }}
34+
steps:
35+
- uses: actions/checkout@v2
36+
- name: Use Node.js ${{ matrix.node-version }}
37+
uses: actions/setup-node@v1
38+
with:
39+
node-version: ${{ matrix.node-version }}
40+
- name: Install yarn
41+
run: npm install -g yarn
42+
- name: Download build
43+
uses: actions/download-artifact@v2
44+
with:
45+
name: artifact
46+
path: dist
47+
- name: Install dev dependencies
48+
run: yarn
49+
- name: Run tests
50+
run: yarn test:ci
51+
52+
npm-publish:
53+
needs: [test]
54+
name: npm-publish
55+
runs-on: ubuntu-latest
56+
# only on master
57+
if: github.ref == 'refs/heads/master'
58+
steps:
59+
- uses: actions/checkout@v2
60+
- uses: actions/setup-node@v1
61+
with:
62+
node-version: 10.x
63+
- name: Download build
64+
uses: actions/download-artifact@v2
65+
with:
66+
name: artifact
67+
path: dist
68+
- name: Publish if version in package.json change
69+
uses: springtype-org/github-action-npm-publish@master
70+
with:
71+
install_build_packages: true
72+
project_build_dir: dist
73+
create_tag: true
74+
auth_token: ${{ secrets.npm_token }}
75+
env:
76+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/dist/
2+
/node_modules/
3+
/.idea/

.npmignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
.DS_Store
3+
__test__

CONTRIBUTORS.md

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## Maintainers
2+
3+
SpringType is brought to you by:
4+
5+
<table>
6+
<tbody>
7+
<tr>
8+
<td align="center">
9+
<img width="150" height="150"
10+
src="https://avatars3.githubusercontent.com/u/454817?v=4&s=150">
11+
</br>
12+
<a href="https://github.com/kyr0">Aron Homberg</a>
13+
</td>
14+
<td align="center">
15+
<img width="150" height="150"
16+
src="https://avatars2.githubusercontent.com/u/12079044?s=150&v=4">
17+
</br>
18+
<a href="https://github.com/mansi1">Michael Mannseicher</a>
19+
</td>
20+
</tr>
21+
<tbody>
22+
</table>
23+
24+
### Contributors
25+
26+
We'd like to thank you for your help to support this project:
27+
28+
- Tom ([jsdevtom](https://github.com/jsdevtom))

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 - 2019 Aron Homberg, Michael Mannseicher & SpringType contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# st-validate
2+
3+
`this package contains validators also decorator to validate interfaces / inputs`
4+
5+
<h2 align="center">Backers</h2>
6+
7+
Thank you so much for supporting us financially! 🙏🏻😎🥳👍
8+
9+
<table>
10+
<tbody>
11+
<tr>
12+
<td align="center">
13+
<img width="150" height="150"
14+
src="https://avatars2.githubusercontent.com/u/17221813?v=4&s=150">
15+
</br>
16+
<a href="https://github.com/jsdevtom">Tom</a>
17+
</td>
18+
</tr>
19+
<tbody>
20+
</table>
21+
22+
<h2 align="center">Maintainers</h2>
23+
24+
SpringType is brought to you by:
25+
26+
<table>
27+
<tbody>
28+
<tr>
29+
<td align="center">
30+
<img width="150" height="150"
31+
src="https://avatars3.githubusercontent.com/u/454817?v=4&s=150">
32+
</br>
33+
<a href="https://github.com/kyr0">Aron Homberg</a>
34+
</td>
35+
<td align="center">
36+
<img width="150" height="150"
37+
src="https://avatars2.githubusercontent.com/u/12079044?s=150&v=4">
38+
</br>
39+
<a href="https://github.com/mansi1">Michael Mannseicher</a>
40+
</td>
41+
</tr>
42+
<tbody>
43+
</table>
44+
45+
<h2 align="center">Contributing</h2>
46+
47+
Please help out to make this project even better and see your name added to the list of our
48+
[CONTRIBUTORS.md](./CONTRIBUTORS.md) :tada:

jest.config.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
process.env.BASE_PATH = __dirname;
2+
3+
module.exports = {
4+
globals: {
5+
"ts-jest": {
6+
diagnostics: false,
7+
tsConfig: "tsconfig.json"
8+
}
9+
},
10+
transform: {
11+
"^.+\\.tsx?$": "ts-jest"
12+
},
13+
testRegex: "(/(__test__)/.*|(\\.|/))\\.test\\.tsx?$",
14+
modulePathIgnorePatterns: ["/modules", "/_modules"],
15+
testPathIgnorePatterns: ["node_modules/", "dist/"],
16+
maxConcurrency: 25,
17+
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "node", "json"],
18+
coveragePathIgnorePatterns: []
19+
};

package.json

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "st-validate",
3+
"version": "1.0.0",
4+
"publishConfig": {
5+
"access": "public"
6+
},
7+
"description": "A validation framework.",
8+
"repository": {
9+
"type": "git",
10+
"url": "git+https://github.com/springtype-org/st-validate.git"
11+
},
12+
"scripts": {
13+
"clean": "npx st-rm-rf dist",
14+
"prebuild": "yarn clean",
15+
"build": "tsc",
16+
"postbuild": "npx st-cp package.json .npmignore README.md LICENSE dist",
17+
"watch": "tsc -w",
18+
19+
"test": "jest --verbose --watch",
20+
"test:ci": "jest --verbose --runInBand",
21+
22+
"coverage": "jest --verbose --coverage",
23+
"coverage:ci": "jest --verbose --coverage --runInBand"
24+
},
25+
"author": "Aron Homberg <[email protected]>, Michael Mannseicher <[email protected]>",
26+
"license": "MIT",
27+
"devDependencies": {
28+
"@types/jest": "^25.2.1",
29+
"@types/node": "^13.13.2",
30+
"jest": "^25.4.0",
31+
"ts-jest": "^25.4.0",
32+
"ts-node": "^8.9.1",
33+
"typescript": "^3.8.3"
34+
}
35+
}

src/decorator/validation.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { IValidator } from "../interface/ivalidator";
2+
import { VALIDATOR_DEFAULT, VALIDATION_DECORATOR_METADATA_KEY } from "..";
3+
import { registerValidation } from "../function/register-validation";
4+
5+
export const validation = (validator: IValidator = VALIDATOR_DEFAULT) =>
6+
<T extends { new(...args: any[]): {} }>(ctor: T) => {
7+
8+
for (const propertyName of Object.keys(ctor.prototype[VALIDATION_DECORATOR_METADATA_KEY])) {
9+
const descriptor = Object.getOwnPropertyDescriptor(ctor.prototype, propertyName);
10+
11+
if (descriptor && typeof ctor.prototype[propertyName] == "function") {
12+
registerValidation(validator).apply(null, [ctor.prototype, propertyName, descriptor]);
13+
Object.defineProperty(ctor.prototype, propertyName, descriptor);
14+
}
15+
}
16+
return ctor;
17+
};
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export function getDecoratorParamNames(fn: Function) {
2+
const src = fn.toString().replace(/\/\*.*\*\//, '');
3+
const pos1 = src.indexOf('(');
4+
const pos2 = src.indexOf(')');
5+
const paramSrc = src.substring(pos1 + 1, pos2);
6+
let params: Array<string> = [];
7+
8+
if (pos1 === -1) {
9+
params = [src.split('=>')[0].trim()];
10+
} else {
11+
12+
let params_ = paramSrc.split(',');
13+
14+
for (let i = 0; i < params_.length; ++i) {
15+
16+
let paramName = params_[i].trim();
17+
18+
if (paramName) {
19+
params.push(paramName);
20+
}
21+
}
22+
}
23+
return params;
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ValidateFunction } from "../interface/validate-function";
2+
import { IValidationRegistration } from "../interface/ivalidation-registration";
3+
import { VALIDATION_DECORATOR_METADATA_KEY } from "..";
4+
5+
export const getParameterValidateDecorator = (validateFn: ValidateFunction, validateName: string): ParameterDecorator => {
6+
return (ctor: any, methodName: string | symbol, parameterIndex: number) => {
7+
8+
if (!ctor[VALIDATION_DECORATOR_METADATA_KEY]) {
9+
ctor[VALIDATION_DECORATOR_METADATA_KEY] = {};
10+
}
11+
12+
const validationRegistrations: Array<IValidationRegistration> = ctor[VALIDATION_DECORATOR_METADATA_KEY][methodName] || [];
13+
14+
ctor[VALIDATION_DECORATOR_METADATA_KEY][methodName] = validationRegistrations.concat({
15+
parameterIndex,
16+
validateFn,
17+
validatorName: validateName
18+
});
19+
};
20+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ValidationResult } from "../interface/ivalidation-result";
2+
3+
export const getValidationErrorMessage = (error: ValidationResult): string => {
4+
return `@${error.validatorName} in class ${error.className}:${error.methodName}(...): Method parameter (name=${error.argumentName}, index=${error.index}) is invalid (value=${error.input})`;
5+
}

src/function/register-validation.ts

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { IValidator } from "../interface/ivalidator";
2+
import { VALIDATOR_DEFAULT, VALIDATION_DECORATOR_METADATA_KEY } from "..";
3+
import { IValidationRegistration } from "../interface/ivalidation-registration";
4+
import { getDecoratorParamNames } from "./get-decorator-param-names";
5+
import { ValidationResult } from "../interface/ivalidation-result";
6+
7+
export const registerValidation = (validator: IValidator = VALIDATOR_DEFAULT) =>
8+
(prototype: any, methodName: string, descriptor: TypedPropertyDescriptor<any>) => {
9+
10+
let method = descriptor.value;
11+
12+
const paramNames = getDecoratorParamNames(prototype[methodName]);
13+
14+
descriptor.value = function() {
15+
16+
const validationRegistrations: Array<IValidationRegistration> = prototype[VALIDATION_DECORATOR_METADATA_KEY][methodName] || [];
17+
const errors: Array<ValidationResult> = [];
18+
19+
for (const validationRegistration of validationRegistrations) {
20+
21+
const parameterIndex = validationRegistration.parameterIndex;
22+
const input = arguments[parameterIndex];
23+
if (!validationRegistration.validateFn(input)) {
24+
errors.push({
25+
argumentName: paramNames[parameterIndex],
26+
index: parameterIndex,
27+
methodName,
28+
className: (this.constructor as any).name,
29+
input: arguments[parameterIndex],
30+
validatorName: validationRegistration.validatorName
31+
})
32+
}
33+
}
34+
if (errors.length > 0) {
35+
validator.validate(errors, this);
36+
} else {
37+
return method.apply(this, arguments);
38+
}
39+
}
40+
};
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const VALIDATOR_NAME = 'VALIDATOR_NAME';
2+
3+
export const validatorNameFactory = (validationFunction: any, validatorName: string) => {
4+
validationFunction[VALIDATOR_NAME] = validatorName;
5+
return validationFunction;
6+
};

src/index.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { IValidator } from "./interface/ivalidator";
2+
import { AssertValidator } from "./validator/assert";
3+
import { PrintValidator } from "./validator/print";
4+
import { ValidationOptions } from "./interface/validation-options";
5+
6+
export { registerValidation as validate } from "./function/register-validation";
7+
export { validation } from "./decorator/validation";
8+
9+
export * from "./validate/index";
10+
11+
export const ASSERT_VALIDATOR: IValidator = new AssertValidator(console.error);
12+
export const VALIDATOR_DEFAULT = ASSERT_VALIDATOR;
13+
export const PRINT_VALIDATOR: IValidator = new PrintValidator(console.error);
14+
export const VALIDATOR_OPTIONS_DEFAULT: ValidationOptions = { required: true };
15+
16+
export const VALIDATION_METHOD_PARAMNAMES_METADATA = "ParamNames";
17+
export const VALIDATION_DECORATOR_METADATA_KEY = "Validation";

src/interface/i-validator-tuple.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface IValidatorTuple {
2+
function: () => boolean;
3+
name: string;
4+
}
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { ValidationResult } from "./ivalidation-result";
2+
3+
export interface IValidationLifecycle {
4+
onValid(validationResult: ValidationResult): void;
5+
onInvalid(validationResult: ValidationResult): void;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { ValidateFunction } from "./validate-function";
2+
3+
export interface IValidationRegistration {
4+
parameterIndex: number;
5+
validateFn: ValidateFunction;
6+
validatorName: string;
7+
}

0 commit comments

Comments
 (0)