-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ [merge#6]: Implement robust descriptor pattern
✨ [feat#6]: Implement robust descriptor pattern
- Loading branch information
Showing
24 changed files
with
569 additions
and
527 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "ts-node", | ||
"type": "node", | ||
"request": "launch", | ||
"args": ["${relativeFile}"], | ||
"runtimeArgs": ["-r", "ts-node/register"], | ||
"cwd": "${workspaceRoot}", | ||
"protocol": "inspector", | ||
"internalConsoleOptions": "openOnSessionStart" | ||
} | ||
] | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,21 @@ | ||
import EntityProcessor from "../src/model/processor/EntityProcessor"; | ||
import validators from "../validators/index"; | ||
import MetadataProcessor from "../src/model/processor/MetadataProcessor"; | ||
import Required from "../validators/any/Required"; | ||
import foreach from "../validators/array/foreach"; | ||
|
||
class TestClass { | ||
@validators.string.Required() | ||
username!: string; | ||
@foreach(Required()) | ||
array: string[] = []; | ||
} | ||
|
||
const processor = new EntityProcessor(TestClass); | ||
console.log(processor.validate({})); | ||
const res = processor.validate({ | ||
array: ["", "2", "3"], | ||
}); | ||
console.log(res); | ||
|
||
console.log(res.valid); | ||
|
||
const meta = MetadataProcessor.inferFrom(TestClass); | ||
//console.log(meta.data); | ||
debugger; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { ValidationGroup } from "../../decorators/types/DecoratorProps.type"; | ||
import { Class } from "../../types/Class.type"; | ||
import { getClassFieldNames } from "../../utils/class.utils"; | ||
import MetadataProcessor from "../processor/MetadataProcessor"; | ||
import FieldDescriptor, { PropertyTypeGroup } from "./FieldDescriptor"; | ||
|
||
export interface IDescriptor<TClass = unknown> { | ||
class?: Class<TClass>; | ||
default?: TClass; | ||
strategy: PropertyTypeGroup; | ||
name: string; | ||
} | ||
|
||
export type Descriptor<TField = unknown> = | ||
| FieldDescriptor<TField> | ||
| ClassDescriptor<TField>; | ||
|
||
export type Descriptors<TClass> = Record<keyof TClass, Descriptor<unknown>>; | ||
|
||
export default class ClassDescriptor<TClass = unknown> | ||
implements IDescriptor<TClass> | ||
{ | ||
#default: TClass; | ||
#class: Class<TClass>; | ||
#groups: ValidationGroup[]; | ||
#schema: Descriptors<TClass>; | ||
#name?: string; | ||
|
||
get name() { | ||
return (this.#name ?? "") as string; | ||
} | ||
|
||
get strategy(): PropertyTypeGroup { | ||
return Array.isArray(this.#default) ? "OBJECT_ARRAY" : "OBJECT"; | ||
} | ||
|
||
get default() { | ||
return this.#default; | ||
} | ||
|
||
get groups() { | ||
return this.#groups; | ||
} | ||
|
||
get class() { | ||
return this.#class; | ||
} | ||
|
||
get schema() { | ||
return this.#schema; | ||
} | ||
|
||
constructor( | ||
clazz: Class<TClass>, | ||
defaultValue: TClass, | ||
groups: ValidationGroup[], | ||
name: string | undefined = undefined | ||
) { | ||
this.#name = name; | ||
this.#default = defaultValue; | ||
this.#class = clazz; | ||
this.#groups = groups; | ||
this.#schema = this.#buildSchema(); | ||
} | ||
|
||
#buildSchema(): Descriptors<TClass> { | ||
return getClassFieldNames(this.#class).reduce((result, field) => { | ||
const meta = MetadataProcessor.inferFrom(this.#class); | ||
const fieldDescriptor = meta.field(field); | ||
const innerDefault = this.#default?.[field]; | ||
this.#applyDefaults(fieldDescriptor, innerDefault!); | ||
const innerClass = fieldDescriptor.class; | ||
const strategy = fieldDescriptor.strategy; | ||
const isClassDescriptor = ["OBJECT_ARRAY", "OBJECT"].includes(strategy); | ||
|
||
const descriptor: Descriptor<unknown> = isClassDescriptor | ||
? new ClassDescriptor( | ||
innerClass!, | ||
innerDefault as any, | ||
this.#groups, | ||
field as string | ||
) | ||
: fieldDescriptor; | ||
|
||
return { | ||
...result, | ||
[field]: descriptor, | ||
}; | ||
}, {}) as Descriptors<TClass>; | ||
} | ||
|
||
#applyDefaults<TKey extends keyof TClass>( | ||
descriptor: FieldDescriptor<unknown>, | ||
defaultValue: TClass[TKey] | ||
) { | ||
descriptor.default ??= defaultValue; | ||
descriptor.host = this.#class; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { Class } from "../../types/Class.type"; | ||
import { IDescriptor } from "./ClassDescriptor"; | ||
import FieldDescriptorRule from "./FieldDescriptorRule"; | ||
|
||
export type PropertyTypeGroup = | ||
| "PRIMITIVE_ARRAY" | ||
| "OBJECT_ARRAY" | ||
| "OBJECT" | ||
| "PRIMITIVE"; | ||
|
||
export type FieldDescriptorRules<FieldType> = { | ||
root: FieldDescriptorRule<FieldType>; | ||
foreach: FieldDescriptorRule<FieldType>; | ||
}; | ||
|
||
export default class FieldDescriptor<FieldType> | ||
implements IDescriptor<FieldType> | ||
{ | ||
#rules: FieldDescriptorRules<FieldType>; | ||
#name: string; | ||
host?: Class<unknown>; | ||
|
||
default?: FieldType; | ||
class?: Class<any>; | ||
|
||
get name(): string { | ||
return this.#name ?? ""; | ||
} | ||
|
||
get rules() { | ||
return this.#rules; | ||
} | ||
|
||
constructor(name: string) { | ||
this.#name = name; | ||
this.#rules = { | ||
root: new FieldDescriptorRule(), | ||
foreach: new FieldDescriptorRule(), | ||
}; | ||
} | ||
|
||
get strategy(): PropertyTypeGroup { | ||
return Array.isArray(this.default) | ||
? this.class | ||
? "OBJECT_ARRAY" | ||
: "PRIMITIVE_ARRAY" | ||
: this.class | ||
? "OBJECT" | ||
: "PRIMITIVE"; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { ValidationGroup } from "../../decorators/types/DecoratorProps.type"; | ||
import { Payload } from "../../types/Payload.type"; | ||
import { ValidationMetadata } from "../../types/ValidationMetadata.type"; | ||
import { ValidationResult } from "../../types/ValidationResult.type"; | ||
import { isValidationGroupUnion } from "../../utils/decorator.utils"; | ||
|
||
export default class FieldDescriptorRule<TFieldType> { | ||
#contents: ValidationMetadata<TFieldType>[]; | ||
|
||
get contents() { | ||
return this.#contents; | ||
} | ||
|
||
constructor() { | ||
this.#contents = []; | ||
} | ||
|
||
validate<TBody>( | ||
value: TFieldType, | ||
payload: Payload<TBody>, | ||
groups: ValidationGroup[] | ||
): ValidationResult[] { | ||
return this.validators(groups) | ||
.map(({ validate }) => validate(value, payload)) | ||
.filter(({ valid }) => !valid); | ||
} | ||
|
||
validators(groups: ValidationGroup[]) { | ||
return this.contents.filter((meta) => | ||
isValidationGroupUnion(groups, meta.groups) | ||
); | ||
} | ||
|
||
pop() { | ||
return this.#contents.pop()!; | ||
} | ||
|
||
add(rule: ValidationMetadata<TFieldType>) { | ||
this.#contents.push(rule); | ||
} | ||
} |
Oops, something went wrong.