Skip to content

Commit

Permalink
✨ [merge#6]: Implement robust descriptor pattern
Browse files Browse the repository at this point in the history
✨ [feat#6]: Implement robust descriptor pattern
  • Loading branch information
brunotot authored Sep 10, 2023
2 parents 43a0fca + ed60247 commit 2f9e3db
Show file tree
Hide file tree
Showing 24 changed files with 569 additions and 527 deletions.
15 changes: 15 additions & 0 deletions .vscode/launch.json
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"
}
]
}
25 changes: 0 additions & 25 deletions packages/core/dev/global.d.ts

This file was deleted.

19 changes: 15 additions & 4 deletions packages/core/dev/playground.ts
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;
4 changes: 2 additions & 2 deletions packages/core/src/decorators/facade/validator.facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ function saveMetadata(
groups: ValidationGroupProp,
isValid: ValidationEvaluator<any>
) {
const validate = processor.getValidationProcessor(key);
validate.appendNode({
const validate = processor.field(key);
validate.rules.root.add({
groups: getSanitizedGroups(groups),
validate: isValid,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type DecoratorContextMetadata = Record<PropertyKey, unknown>;
export type DecoratorContextMetadata = DecoratorMetadata;

export type DecoratorContext<Accept = unknown> = Readonly<{
kind: "field" | "method" | "getter";
Expand Down
99 changes: 99 additions & 0 deletions packages/core/src/model/descriptor/ClassDescriptor.ts
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;
}
}
51 changes: 51 additions & 0 deletions packages/core/src/model/descriptor/FieldDescriptor.ts
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";
}
}
41 changes: 41 additions & 0 deletions packages/core/src/model/descriptor/FieldDescriptorRule.ts
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);
}
}
Loading

0 comments on commit 2f9e3db

Please sign in to comment.