diff --git a/benchmark/src/HObjectParseBenchmark.mts b/benchmark/src/HObjectParseBenchmark.mts index 6c83504..086b56e 100644 --- a/benchmark/src/HObjectParseBenchmark.mts +++ b/benchmark/src/HObjectParseBenchmark.mts @@ -1,27 +1,32 @@ import { Bench } from 'tinybench'; import { - ValueObject, UIntValue, Dto, type JsonObjectType, - type PlainParsableHObjectType, type R, type PlainParseError, PlainParseHelper, + NumberPlainParseHelper, + StringPlainParseHelper, + ArrayPlainParseHelper, + IntegerPlainParseHelper, InvalidTypePlainParseIssue, PlainParseIssue, OK, HObjectTypeMeta, TooBigPlainParseIssue, + RefId, + v, + DtoType, + AnyDto, } from "@hexancore/common"; import { union, z } from 'zod'; -@ValueObject('Test') export class TestValueObject extends UIntValue { public static HOBJ_META = HObjectTypeMeta.application('core', 'core', 'value_object', 'Test', TestValueObject); } -class OtherTestDto extends Dto { +class OtherTestDto extends Dto { public static HOBJ_META = HObjectTypeMeta.application('core', 'core', 'dto', 'OtherTest', OtherTestDto); public constructor( @@ -31,7 +36,7 @@ class OtherTestDto extends Dto { } // AOT generated example - public static parse(this: PlainParsableHObjectType, plain: unknown): R { + public static parse(this: DtoType, plain: unknown): R { // constant check part if (typeof plain !== 'object') { return PlainParseHelper.HObjectParseErr(this, [new InvalidTypePlainParseIssue('object', typeof plain)]); @@ -41,7 +46,7 @@ class OtherTestDto extends Dto { const issues: PlainParseIssue[] = []; // end constant check part - const primitiveField = PlainParseHelper.parseNumber(plainObj.primitiveField, 'primitiveField', issues); + const primitiveField = NumberPlainParseHelper.parseNumber(plainObj.primitiveField, 'primitiveField', issues); if (!(primitiveField instanceof PlainParseIssue) && primitiveField > 2000) { issues.push(TooBigPlainParseIssue.numberLT(2000, primitiveField)); } @@ -62,7 +67,7 @@ class OtherTestDto extends Dto { } } -export class TestDto extends Dto { +export class TestDto extends Dto { public static HOBJ_META = HObjectTypeMeta.application('core', 'core', 'dto', 'Test', TestDto); @@ -82,7 +87,7 @@ export class TestDto extends Dto { } // AOT generated example - public static parse(this: PlainParsableHObjectType, plain: unknown): R { + public static parse(this: DtoType, plain: unknown): R { // constant check part if (typeof plain !== 'object') { return PlainParseHelper.HObjectIsNotObjectParseErr(TestDto as any, plain); @@ -94,9 +99,9 @@ export class TestDto extends Dto { const bigIntField = PlainParseHelper.parseBigInt64(p.bigIntField, 'bigIntField', issues); - const numberField = PlainParseHelper.parseNumberLT(p.numberField, 2000, 'numberField', issues); + const numberField = NumberPlainParseHelper.parseNumberLT(p.numberField, 2000, 'numberField', issues); - const numberArrayField = PlainParseHelper.parsePrimitiveArray(p.numberArrayField, PlainParseHelper.parseNumber, 'numberArrayField', issues); + const numberArrayField = ArrayPlainParseHelper.parsePrimitiveArray(p.numberArrayField, NumberPlainParseHelper.parseNumber, 'numberArrayField', issues); const booleanField = PlainParseHelper.parseBoolean(p.booleanField, 'booleanField', issues); let valueObjectField; @@ -106,7 +111,7 @@ export class TestDto extends Dto { let optionalValueObjectArrayField; if (p.optionalValueObjectArrayField) { - optionalValueObjectArrayField = PlainParseHelper.parseHObjectArray(p.optionalValueObjectArrayField, TestValueObject, 'optionalValueObjectArrayField', issues); + optionalValueObjectArrayField = ArrayPlainParseHelper.parseHObjectArray(p.optionalValueObjectArrayField, TestValueObject, 'optionalValueObjectArrayField', issues); } let optionalDtoField; @@ -116,7 +121,7 @@ export class TestDto extends Dto { let optionalDtoArrayField; if (p.optionalDtoArrayField !== undefined) { - optionalDtoArrayField = PlainParseHelper.parseHObjectArray(p.optionalDtoArrayField, OtherTestDto, 'optionalDtoArrayField', issues); + optionalDtoArrayField = ArrayPlainParseHelper.parseHObjectArray(p.optionalDtoArrayField, OtherTestDto, 'optionalDtoArrayField', issues); } let unionField; @@ -136,7 +141,7 @@ export class TestDto extends Dto { } if (issues.length > 0) { - return PlainParseHelper.HObjectParseErr(TestDto, issues); + return PlainParseHelper.HObjectParseErr(TestDto as any, issues); } return OK(new this( @@ -169,6 +174,164 @@ export class TestDto extends Dto { } } +export class SmallTestDto extends Dto { + + public static HOBJ_META = HObjectTypeMeta.application('core', 'core', 'dto', 'SmallTestDto', TestDto); + + public constructor( + public stringField: string, + public numberField: number, + public numberArrayField: number[], + public booleanField: boolean, + public valueObjectField: RefId, + ) { + super(); + } + + // AOT generated example + public static parse(this: DtoType, plain: unknown): R { + // constant check part + if (typeof plain !== 'object') { + return PlainParseHelper.HObjectIsNotObjectParseErr(SmallTestDto, plain); + } + + const p = plain as Record; + const issues: PlainParseIssue[] = []; + // end constant check part + + + const stringField = StringPlainParseHelper.parseString(p.stringField, 'stringField', issues); + const numberField = NumberPlainParseHelper.parseNumberLT(p.numberField, 2000, 'numberField', issues); + + const numberArrayField = ArrayPlainParseHelper.parsePrimitiveArray(p.numberArrayField, NumberPlainParseHelper.parseNumber, 'numberArrayField', issues); + const booleanField = PlainParseHelper.parseBoolean(p.booleanField, 'booleanField', issues); + + const valueObjectField = PlainParseHelper.parseHObject(p.valueObjectField, TestValueObject, 'optionalValueObjectField', issues); + + return OK(new SmallTestDto( + stringField as any, + numberField as any, + numberArrayField as any, + booleanField as any, + valueObjectField as any, + )) as any; + } + + + // AOT generated example + public toJSON(): JsonObjectType { + return { + stringField: this.stringField, + numberField: this.numberField, + numberArrayField: this.numberArrayField, + booleanField: this.booleanField, + valueObjectField: this.valueObjectField?.toJSON(), + }; + } +} + +class TestTransformDto extends Dto { + public static HOBJ_META = HObjectTypeMeta.application("Book", "Book", "Dto", "TestTransformDto", TestTransformDto); + public optionalField?: string; + public numberField!: number; + public stringField!: string; + public booleanField!: boolean; + public bigintField!: bigint; + public primitiveArrayField!: string[]; + public uintField!: v.uint; + public ruleWithArgsField!: v.int.between<-10, 100>; + public ruleArrayField!: v.int.between<-10, 100>[]; + //public ruleArrayWithItemsField!: v.int.between<-10, 100>[] & v.items.between<2, 5>; + public hObjField!: RefId; + public optionalHObjField?: RefId; + public hObjArrayField!: RefId[]; + public constructor(numberField: any, stringField: any, booleanField: any, bigintField: any, primitiveArrayField: any, uintField: any, ruleWithArgsField: any, ruleArrayField: any, hObjField: any, hObjArrayField: any, optionalField?: any, optionalHObjField?: any) { + super(); + this.numberField = numberField; + this.stringField = stringField; + this.booleanField = booleanField; + this.bigintField = bigintField; + this.primitiveArrayField = primitiveArrayField; + this.uintField = uintField; + this.ruleWithArgsField = ruleWithArgsField; + this.ruleArrayField = ruleArrayField; + this.hObjField = hObjField; + this.hObjArrayField = hObjArrayField; + this.optionalField = optionalField; + this.optionalHObjField = optionalHObjField; + } + public static parse(this: DtoType, plain: unknown): R { + if (typeof plain !== "object") { + return PlainParseHelper.HObjectIsNotObjectParseErr(TestTransformDto as any, plain); + } + const p = plain as Record; + const issues: PlainParseIssue[] = []; + let optionalField; + if (p.optionalField !== undefined) { + optionalField = StringPlainParseHelper.parseString(p.optionalField, "optionalField", issues); + } + const numberField = NumberPlainParseHelper.parseNumber(p.numberField, "numberField", issues); + const stringField = StringPlainParseHelper.parseString(p.stringField, "stringField", issues); + const booleanField = PlainParseHelper.parseBoolean(p.booleanField, "booleanField", issues); + const bigintField = PlainParseHelper.parseBigInt64(p.bigintField, "bigintField", issues); + const primitiveArrayField = ArrayPlainParseHelper.parsePrimitiveArray(p.primitiveArrayField, pi => StringPlainParseHelper.parseString(pi), "primitiveArrayField", issues); + const uintField = IntegerPlainParseHelper.parseUInt(p.uintField, "uintField", issues); + const ruleWithArgsField = IntegerPlainParseHelper.parseIntBetween(p.ruleWithArgsField, -10, 100, "ruleWithArgsField", issues); + const ruleArrayField = ArrayPlainParseHelper.parsePrimitiveArray(p.ruleArrayField, pi => IntegerPlainParseHelper.parseIntBetween(pi, -10, 100), "ruleArrayField", issues); + const hObjField = PlainParseHelper.parseHObject(p.hObjField, RefId, "hObjField", issues); + let optionalHObjField; + if (p.optionalHObjField !== undefined) { + optionalHObjField = PlainParseHelper.parseHObject(p.optionalHObjField, RefId, "optionalHObjField", issues); + } + const hObjArrayField = ArrayPlainParseHelper.parseHObjectArray(p.hObjArrayField, RefId, "hObjArrayField", issues); + if (issues.length > 0) { + return PlainParseHelper.HObjectParseErr(TestTransformDto as any, issues); + } + return OK(new TestTransformDto(optionalField as any, numberField as any, stringField as any, booleanField as any, bigintField as any, primitiveArrayField as any, uintField as any, ruleWithArgsField as any, ruleArrayField as any, hObjField as any, optionalHObjField as any, hObjArrayField as any)) as any; + } + public toJSON(): JsonObjectType { + return { + optionalField: this.optionalField, + numberField: this.numberField, + stringField: this.stringField, + booleanField: this.booleanField, + bigintField: this.bigintField.toString(), + primitiveArrayField: this.primitiveArrayField, + uintField: this.uintField, + ruleWithArgsField: this.ruleWithArgsField, + ruleArrayField: this.ruleArrayField, + hObjField: this.hObjField.toJSON(), + optionalHObjField: this.optionalHObjField?.toJSON(), + hObjArrayField: this.hObjArrayField.map(item => item.toJSON()) + }; + } +} + +const plain2 = { + "bigintField": "10", + "booleanField": true, + "hObjArrayField": [ + "test_1", + "test_2", + ], + "hObjField": "test", + "numberField": 1, + "optionalField": "test_optionalStringField", + "optionalHObjField": "test_1", + "primitiveArrayField": [ + "test1", + "test2", + ], + "ruleArrayField": [ + -5, + 50, + ], + "ruleWithArgsField": -5, + "stringField": "test_stringField", + "uintField": 100, +}; + + const plain: JsonObjectType = { bigIntField: '1000', @@ -187,6 +350,30 @@ const plain: JsonObjectType = { optionalDtoArrayField: [{ primitiveField: 1000 }, { primitiveField: 2000 }] }; +const smallPlain: JsonObjectType = { + stringField: 'test_string', + numberField: 1000, + numberArrayField: [1000], + booleanField: true, + valueObjectField: '1000', +}; + +const invalidSmallPlain: JsonObjectType | any = { + stringField: 1008, + numberField: 1000, + numberArrayField: [1000], + booleanField: "fog", + valueObjectField: '1000', +}; + +const zodSmallNoExtraClassesTestDtoSchema = z.object({ + stringField: z.string(), + numberField: z.number(), + numberArrayField: z.array(z.number()), + booleanField: z.boolean(), + valueObjectField: z.string(), +}); + const invalidPlain: JsonObjectType | any = { bigIntField: '1000', @@ -239,11 +426,33 @@ const zodNoTransformTestDtoSchema = z.object({ optionalDtoArrayField: z.array(zodOtherTestDtoSchema).optional(), }); -const bench = new Bench({ time: 3000, iterations: 12, warmupTime: 500 }); +const bench = new Bench({ time: 3000, iterations: 8, warmupTime: 500 }); bench + .add('Small HObject.parse() - valid plain', () => { + const obj = SmallTestDto.parse(smallPlain); + if (obj.isError()) { + throw new Error("imposible"); + } + }) + .add('Small HObject.parse() - invalid plain', () => { + const obj = SmallTestDto.parse(invalidSmallPlain); + }) + + .add('Small Zod(Pure) - valid plain', () => { + const obj = zodSmallNoExtraClassesTestDtoSchema.parse(smallPlain); + }) + + .add('Small Zod(Pure) - invalid plain', () => { + try { + const obj = zodSmallNoExtraClassesTestDtoSchema.parse(invalidSmallPlain); + // eslint-disable-next-line no-empty + } catch (e) { + } + }) + .add('HObject.parse - valid plain', () => { - const obj = TestDto.parse(plain); + const obj = TestTransformDto.parse(plain2); if (obj.isError()) { throw new Error("imposible"); } @@ -257,9 +466,6 @@ bench }) .add('HObject.parse - invalid plain', () => { const obj = TestDto.parse(invalidPlain); - if (obj.isError()) { - - } }) .add('Zod - with-transforms - invalid plain', () => { try { diff --git a/src/Domain/ValueObject/AbstractValueObject.ts b/src/Domain/ValueObject/AbstractValueObject.ts index 6952fc2..7c4c3c2 100644 --- a/src/Domain/ValueObject/AbstractValueObject.ts +++ b/src/Domain/ValueObject/AbstractValueObject.ts @@ -16,7 +16,7 @@ export abstract class AbstractValueObject implements J * @param plain * @returns */ - public static parse(this: (new (...args: any[]) => any), plain: unknown): R { + public static parse(this: ValueObjectType, plain: unknown): R { throw new LogicError('Not implemented or AOT generated'); } diff --git a/src/Domain/ValueObject/DateTime.ts b/src/Domain/ValueObject/DateTime.ts index 218d642..62e48d2 100644 --- a/src/Domain/ValueObject/DateTime.ts +++ b/src/Domain/ValueObject/DateTime.ts @@ -1,9 +1,8 @@ -import { AbstractValueObject, type ValueObjectType } from './AbstractValueObject'; +import { AbstractValueObject, type AnyValueObject, type ValueObjectType } from './AbstractValueObject'; import { OK, R } from '../../Util/Result'; import { DateTimeFormatter, Duration, Instant, LocalDateTime, Period, ZoneId, ZoneOffset, convert } from '@js-joda/core'; -import { HObjectTypeMeta, InvalidStringPlainParseIssue, InvalidTypePlainParseIssue, PlainParseHelper, TooSmallPlainParseIssue, type PlainParsableHObjectType, type PlainParseError } from "../../Util"; +import { HObjectTypeMeta, InvalidStringPlainParseIssue, InvalidTypePlainParseIssue, PlainParseHelper, TooSmallPlainParseIssue, type PlainParseError } from "../../Util"; -export type DateTimeRawType = number; export const DEFAULT_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); function createJsJodaFromString(v: string): LocalDateTime { @@ -44,7 +43,7 @@ export class DateTime extends AbstractValueObject { return new this(LocalDateTime.now(ZoneOffset.UTC)); } - public static parse(this: ValueObjectType, plain: unknown): R { + public static parse(this: ValueObjectType, plain: unknown): R { switch (typeof plain) { case 'number': return DateTime.fromTimestamp(plain) as any; case 'string': @@ -89,6 +88,7 @@ export class DateTime extends AbstractValueObject { * @returns */ public static fromTimestamp(timestamp: number): R { + timestamp = Math.trunc(timestamp); if (timestamp < 0) { return PlainParseHelper.HObjectParseErr(DateTime, [ TooSmallPlainParseIssue.numberGTE(0, timestamp) diff --git a/src/Domain/ValueObject/RegexStringValue.ts b/src/Domain/ValueObject/RegexStringValue.ts index c5dd36d..3f8f8f2 100644 --- a/src/Domain/ValueObject/RegexStringValue.ts +++ b/src/Domain/ValueObject/RegexStringValue.ts @@ -1,18 +1,13 @@ -import { LogicError, OK, PlainParseHelper, PlainParseIssue, type PlainParseError, type R } from '../../Util'; -import { AbstractValueObject, type ValueObjectType } from "./AbstractValueObject"; - -export type RegexStringSubtype = { - new(value: string): T; - getRegex(): RegExp; -} & ValueObjectType; +import { LogicError, OK, StringPlainParseHelper, PlainParseHelper, PlainParseIssue, type PlainParseError, type R } from '../../Util'; +import { AbstractValueObject, type AnyValueObject, type ValueObjectType } from "./AbstractValueObject"; export abstract class RegexStringValue> extends AbstractValueObject { public constructor(public readonly v: string) { super(); } - public static parse>(this: RegexStringSubtype, plain: unknown): R { - const parsed = PlainParseHelper.parseStringRegex(plain, this.getRegex()); + public static parse(this: ValueObjectType & { getRegex(): RegExp; }, plain: unknown): R { + const parsed = StringPlainParseHelper.parseStringRegex(plain, this.getRegex()); if (parsed instanceof PlainParseIssue) { return PlainParseHelper.HObjectParseErr(this, [parsed]); } @@ -29,7 +24,7 @@ export abstract class RegexStringValue> extends * @param v * @returns */ - public static cs>(this: RegexStringSubtype, v: string): T { + public static cs>(this: ValueObjectType, v: string): T { return new (this as any)(v); } @@ -44,4 +39,4 @@ export abstract class RegexStringValue> extends public toJSON(): string { return this.v; } -} +} \ No newline at end of file diff --git a/src/Domain/ValueObject/StringValue.ts b/src/Domain/ValueObject/StringValue.ts index 72eaaf6..3f7524f 100644 --- a/src/Domain/ValueObject/StringValue.ts +++ b/src/Domain/ValueObject/StringValue.ts @@ -1,5 +1,5 @@ -import { HObjectTypeMeta, OK, PlainParseError, PlainParseHelper, PlainParseIssue, type R } from "../../Util"; -import { AbstractValueObject, type ValueObjectType } from "./AbstractValueObject"; +import { HObjectTypeMeta, OK, PlainParseError, PlainParseHelper, StringPlainParseHelper, PlainParseIssue, type R } from "../../Util"; +import { AbstractValueObject, type AnyValueObject, type ValueObjectType } from "./AbstractValueObject"; export class StringValue = any> extends AbstractValueObject { public static readonly HOBJ_META = HObjectTypeMeta.domain('Core', 'Core', 'ValueObject', 'String', StringValue); @@ -9,8 +9,8 @@ export class StringValue = any> extends AbstractValue super(); } - public static parse(this: ValueObjectType, plain: unknown): R { - const parsed = PlainParseHelper.parseString(plain); + public static parse(this: ValueObjectType, plain: unknown): R { + const parsed = StringPlainParseHelper.parseString(plain); if (parsed instanceof PlainParseIssue) { return PlainParseHelper.HObjectParseErr(this, [parsed]); } diff --git a/src/Domain/ValueObject/UBigIntValue.ts b/src/Domain/ValueObject/UBigIntValue.ts index 3a70214..517b7a6 100644 --- a/src/Domain/ValueObject/UBigIntValue.ts +++ b/src/Domain/ValueObject/UBigIntValue.ts @@ -1,5 +1,5 @@ import { HObjectTypeMeta, OK, PlainParseHelper, PlainParseIssue, type PlainParseError, type R } from '../../Util'; -import { AbstractValueObject, type ValueObjectType } from './AbstractValueObject'; +import { AbstractValueObject, type AnyValueObject, type ValueObjectType } from './AbstractValueObject'; export class UBigIntValue = any> extends AbstractValueObject { public static readonly HOBJ_META = HObjectTypeMeta.domain('Core', 'Core', 'ValueObject', 'UBigInt', UBigIntValue); @@ -8,7 +8,7 @@ export class UBigIntValue = any> extends AbstractVal super(); } - public static parse(this: ValueObjectType, plain: unknown): R { + public static parse(this: ValueObjectType, plain: unknown): R { const parsed = PlainParseHelper.parseBigInt64GTE(plain, 0n); if (parsed instanceof PlainParseIssue) { return PlainParseHelper.HObjectParseErr(this, [parsed]); diff --git a/src/Domain/ValueObject/UIntValue.ts b/src/Domain/ValueObject/UIntValue.ts index 12d0eda..3ed9b34 100644 --- a/src/Domain/ValueObject/UIntValue.ts +++ b/src/Domain/ValueObject/UIntValue.ts @@ -1,5 +1,5 @@ -import { HObjectTypeMeta, InvalidTypePlainParseIssue, OK, PlainParseHelper, TooSmallPlainParseIssue, type PlainParseError, type R } from '../../Util'; -import { AbstractValueObject, type ValueObjectType } from './AbstractValueObject'; +import { HObjectTypeMeta, IntegerPlainParseHelper, OK, PlainParseHelper, type PlainParseError, type R } from '../../Util'; +import { AbstractValueObject, type AnyValueObject, type ValueObjectType } from './AbstractValueObject'; export class UIntValue = any> extends AbstractValueObject { public static readonly HOBJ_META = HObjectTypeMeta.domain('Core', 'User', 'ValueObject', 'UInt', UIntValue); @@ -8,25 +8,9 @@ export class UIntValue = any> extends AbstractValueObje super(); } - public static parse(this: ValueObjectType, plain: unknown): R { - let parsed: number; - switch (typeof plain) { - case 'number': - parsed = Math.trunc(plain); - break; - case 'string': - parsed = parseInt(plain); - break; - default: - return PlainParseHelper.HObjectParseErr(this, [new InvalidTypePlainParseIssue('number', typeof plain)]); - } - - if (parsed < 0) { - const issue = TooSmallPlainParseIssue.numberGTE(0, parsed); - return PlainParseHelper.HObjectParseErr(this, [issue]); - } - - return OK(new this(parsed)); + public static parse(this: ValueObjectType, plain: unknown): R { + const parsed = IntegerPlainParseHelper.parseUInt(plain); + return typeof parsed === "number" ? OK(new this(parsed)) : PlainParseHelper.HObjectParseErr(this, [parsed]); } /** @@ -50,7 +34,7 @@ export class UIntValue = any> extends AbstractValueObje return this.v.toString(); } - public toJSON(): string { - return this.v.toString(); + public toJSON(): number { + return this.v; } -} +} \ No newline at end of file diff --git a/src/Util/Dto.ts b/src/Util/Dto.ts index 7d155ba..7308010 100644 --- a/src/Util/Dto.ts +++ b/src/Util/Dto.ts @@ -1,13 +1,14 @@ import { LogicError } from './Error'; -import { HObjectType, type PlainParsableHObjectType } from './Feature/HObjectTypeMeta'; +import { HObjectType, type HObjectTypeMeta } from './Feature/HObjectTypeMeta'; import { JsonSerialize } from './Json/JsonSerialize'; import type { PlainParseError } from './Plain'; import { type R } from './Result'; -import type { NonMethodProperties } from './types'; +import type { JsonObjectType, NonMethodProperties } from './types'; -export type DtoType = HObjectType; +export type AnyDto = Dto; +export type DtoType = HObjectType; -export abstract class Dto implements JsonSerialize { +export abstract class Dto implements JsonSerialize { /** * Create from safe props @@ -15,7 +16,7 @@ export abstract class Dto implements JsonSerialize { * @param props * @returns */ - public static cs(this: DtoType, props: NonMethodProperties): T { + public static cs(this: DtoType, props: NonMethodProperties): T { const i = new this(); Object.assign(i, props); return i; @@ -27,12 +28,11 @@ export abstract class Dto implements JsonSerialize { * @param plain * @returns */ - public static parse(this: PlainParsableHObjectType, plain: unknown): R { + public static parse(this: DtoType, plain: unknown): R { throw new LogicError('Not implemented or AOT generated'); } - public toJSON(): Record { + public toJSON(): JsonObjectType { throw new LogicError('Not implemented or AOT generated'); } - } diff --git a/src/Util/Feature/HObjectTypeMeta.ts b/src/Util/Feature/HObjectTypeMeta.ts index da7c3a0..898a038 100644 --- a/src/Util/Feature/HObjectTypeMeta.ts +++ b/src/Util/Feature/HObjectTypeMeta.ts @@ -36,23 +36,23 @@ export class HObjectTypeMeta implements JsonSerialize { ) { this.typeId = [ pascalCaseToSnakeCase(feature), - layer, + layer.toLowerCase(), pascalCaseToSnakeCase(context), - kind, + pascalCaseToSnakeCase(kind), pascalCaseToSnakeCase(name) ].join('.'); } public static application(feature: string, context: string, kind: string, name: string, typeClass: AnyHObjectType): HObjectTypeMeta { - return new this(feature, 'application', context, kind, name, typeClass); + return new this(feature, 'Application', context, kind, name, typeClass); } public static domain(feature: string, context: string, kind: string, name: string, typeClass: AnyHObjectType): HObjectTypeMeta { - return new this(feature, 'domain', context, kind, name, typeClass); + return new this(feature, 'Domain', context, kind, name, typeClass); } public static infrastructure(feature: string, context: string, kind: string, name: string, typeClass: AnyHObjectType): HObjectTypeMeta { - return new this(feature, 'infrastructure', context, kind, name, typeClass); + return new this(feature, 'Infrastructure', context, kind, name, typeClass); } public static injectToClass(classConstructor: HObjectType, meta: HObjectTypeMeta): void { diff --git a/src/Util/Feature/types.ts b/src/Util/Feature/types.ts index 2687793..5a3919b 100644 --- a/src/Util/Feature/types.ts +++ b/src/Util/Feature/types.ts @@ -1 +1 @@ -export type HFeatureBackendLayer = 'application' | 'domain' | 'infrastructure'; +export type HFeatureBackendLayer = 'Application' | 'Domain' | 'Infrastructure'; diff --git a/src/Util/Plain/ArrayPlainParseHelper.ts b/src/Util/Plain/ArrayPlainParseHelper.ts new file mode 100644 index 0000000..0b8042e --- /dev/null +++ b/src/Util/Plain/ArrayPlainParseHelper.ts @@ -0,0 +1,211 @@ +import { RefId } from "@/Domain"; +import { type PlainParsableHObjectType } from "../Feature"; +import { InvalidArrayElementsPlainParseIssue, InvalidHObjectPlainParseIssue, InvalidTypePlainParseIssue, OutOfRangePlainParseIssue, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "./PlainParseIssue"; + +export type ParseArrayItemFn = (v: unknown) => T | PlainParseIssue; +export class ArrayPlainParseHelper { + + public static parsePrimitiveArrayItemsMin(plain: unknown, minItems: number, parse: ParseArrayItemFn, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length < minItems) { + const issue = TooSmallPlainParseIssue.arrayAtLeastSize(minItems, plain.length, path); + issues?.push(issue); + return issue; + } + + return ArrayPlainParseHelper.parsePrimitiveArray(plain, parse, path, issues); + } + + public static parsePrimitiveArrayItemsMax(plain: unknown, maxItems: number, parse: ParseArrayItemFn, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length > maxItems) { + const issue = TooBigPlainParseIssue.arrayMaxSize(maxItems, plain.length, path); + issues?.push(issue); + return issue; + } + + return ArrayPlainParseHelper.parsePrimitiveArray(plain, parse, path, issues); + } + + public static parsePrimitiveArrayItemsBetween(plain: unknown, minItems: number, maxItems: number, parse: ParseArrayItemFn, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length > maxItems || plain.length < minItems) { + const issue = OutOfRangePlainParseIssue.arrayBetween(minItems, maxItems, plain.length, path); + issues?.push(issue); + return issue; + } + + return ArrayPlainParseHelper.parsePrimitiveArray(plain, parse, path, issues); + } + + public static parsePrimitiveArrayItemsExactly(plain: unknown, exactlyItems: number, parse: ParseArrayItemFn, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length !== exactlyItems) { + const issue = plain.length > exactlyItems + ? TooBigPlainParseIssue.arrayExactlySize(exactlyItems, plain.length, path) + : TooSmallPlainParseIssue.arrayExactlySize(exactlyItems, plain.length, path); + issues?.push(issue); + return issue; + } + + return ArrayPlainParseHelper.parsePrimitiveArray(plain, parse, path, issues); + } + + public static parsePrimitiveArray(plain: unknown, parse: ParseArrayItemFn, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + let localIssues: PlainParseIssue[] | null = null; + const parsedValues: T[] = []; + for (let i = 0; i < plain.length; i++) { + const parsed = parse(plain[i]); + + if (parsed instanceof PlainParseIssue) { + parsed.path = "" + i; + if (!localIssues) { + localIssues = [parsed]; + } else { + localIssues.push(parsed); + } + continue; + } + + if (!localIssues) { + parsedValues.push(parsed); + } + } + + if (localIssues) { + const issue = new InvalidArrayElementsPlainParseIssue(localIssues, path); + issues?.push(issue); + return issue; + } + return parsedValues; + } + + public static parseHObjectArrayItemsMin(plain: unknown, minItems: number, objectClass: PlainParsableHObjectType, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length < minItems) { + const issue = TooSmallPlainParseIssue.arrayAtLeastSize(minItems, plain.length, path); + issues?.push(issue); + return issue; + } + + return ArrayPlainParseHelper.parseHObjectArray(plain, objectClass, path, issues); + } + + public static parseHObjectArrayItemsMax(plain: unknown, maxItems: number, objectClass: PlainParsableHObjectType, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length > maxItems) { + const issue = TooBigPlainParseIssue.arrayMaxSize(maxItems, plain.length, path); + issues?.push(issue); + return issue; + } + + return ArrayPlainParseHelper.parseHObjectArray(plain, objectClass, path, issues); + } + + public static parseHObjectArrayItemsBetween(plain: unknown, minItems: number, maxItems: number, objectClass: PlainParsableHObjectType, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length > maxItems || plain.length < minItems) { + const issue = OutOfRangePlainParseIssue.arrayBetween(minItems, maxItems, plain.length, path); + issues?.push(issue); + return issue; + } + + return ArrayPlainParseHelper.parseHObjectArray(plain, objectClass, path, issues); + } + + + public static parseHObjectArrayItemsExactly(plain: unknown, exactlyItems: number, objectClass: PlainParsableHObjectType, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue("array", typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length !== exactlyItems) { + const issue = plain.length > exactlyItems + ? TooBigPlainParseIssue.arrayExactlySize(exactlyItems, plain.length, path) + : TooSmallPlainParseIssue.arrayExactlySize(exactlyItems, plain.length, path); + issues?.push(issue); + return issue; + } + + return ArrayPlainParseHelper.parseHObjectArray(plain, objectClass, path, issues); + } + + public static parseHObjectArray(plain: unknown, objectClass: PlainParsableHObjectType, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { + if (!Array.isArray(plain)) { + const issue = new InvalidTypePlainParseIssue('array', typeof plain, path); + issues?.push(issue); + return issue; + } + + let localIssues: PlainParseIssue[] | null = null; + const parsedValues: T[] = []; + for (let i = 0; i < plain.length; i++) { + const parsed = objectClass.parse(plain[i]); + + if (parsed.isError()) { + const issue = parsed.e.data as InvalidHObjectPlainParseIssue; + issue.path = "" + i; + if (!localIssues) { + localIssues = [issue]; + } else { + localIssues.push(issue); + } + continue; + } + + if (!localIssues) { + parsedValues.push(parsed.v); + } + } + + if (localIssues) { + const issue = new InvalidArrayElementsPlainParseIssue(localIssues, path); + issues?.push(issue); + return issue; + } + return parsedValues; + } +} \ No newline at end of file diff --git a/src/Util/Plain/IntegerPlainParseHelper.ts b/src/Util/Plain/IntegerPlainParseHelper.ts new file mode 100644 index 0000000..57eec1b --- /dev/null +++ b/src/Util/Plain/IntegerPlainParseHelper.ts @@ -0,0 +1,210 @@ +import { InvalidTypePlainParseIssue, OutOfRangePlainParseIssue, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "./PlainParseIssue"; + +export class IntegerPlainParseHelper { + + public static parseInt(plain: unknown, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('int', typeof plain, path); + issues?.push(issue); + return issue; + } + + return plain as number; + } + + public static parseIntGTE(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('int', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) < min) { + const issue = TooSmallPlainParseIssue.numberGTE(min, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseIntGT(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('int', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) <= min) { + const issue = TooSmallPlainParseIssue.numberGT(min, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseIntLT(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('int', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) >= max) { + const issue = TooBigPlainParseIssue.numberLT(max, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseIntLTE(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('int', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) > max) { + const issue = TooBigPlainParseIssue.numberLTE(max, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseIntBetween(plain: unknown, min: number, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('int', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) < min || (plain as number) > max) { + const issue = OutOfRangePlainParseIssue.numberBetween(min, max, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseIntBetweenExclusively(plain: unknown, min: number, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('int', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) < min || (plain as number) > max) { + const issue = OutOfRangePlainParseIssue.numberBetweenExclusively(min, max, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseUInt(plain: unknown, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('uint', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) < 0) { + const issue = TooSmallPlainParseIssue.numberGTE(0, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + + } + + public static parseUIntGTE(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('uint', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) < min) { + const issue = TooSmallPlainParseIssue.numberGTE(min, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseUIntGT(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('uint', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) <= min) { + const issue = TooSmallPlainParseIssue.numberGT(min, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseUIntLT(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('uint', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) >= max) { + const issue = TooBigPlainParseIssue.numberLT(max, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseUIntLTE(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('uint', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) > max) { + const issue = TooBigPlainParseIssue.numberLTE(max, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseUIntBetween(plain: unknown, min: number, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('uint', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) < min || (plain as number) > max) { + const issue = OutOfRangePlainParseIssue.numberBetween(min, max, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } + + public static parseUIntBetweenExclusively(plain: unknown, min: number, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (!Number.isInteger(plain)) { + const issue = new InvalidTypePlainParseIssue('uint', typeof plain, path); + issues?.push(issue); + return issue; + } + + if ((plain as number) < min || (plain as number) > max) { + const issue = OutOfRangePlainParseIssue.numberBetweenExclusively(min, max, (plain as number), path); + issues?.push(issue); + return issue; + } + return (plain as number); + } +} diff --git a/src/Util/Plain/NumberPlainParseHelper.ts b/src/Util/Plain/NumberPlainParseHelper.ts new file mode 100644 index 0000000..6424569 --- /dev/null +++ b/src/Util/Plain/NumberPlainParseHelper.ts @@ -0,0 +1,92 @@ +import { InvalidTypePlainParseIssue, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "./PlainParseIssue"; + +export class NumberPlainParseHelper { + public static parseNumber(plain: unknown, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (typeof plain === 'number') { + return plain; + } + + const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); + if (issues) { + issues.push(issue); + } + return issue; + } + + public static parseNumberGTE(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (typeof plain === 'number') { + if (plain < min) { + const issue = TooSmallPlainParseIssue.numberGTE(min, plain, path); + issues?.push(issue); + return issue; + } + return plain; + } + + const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); + issues?.push(issue); + return issue; + } + + public static parseNumberGT(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (typeof plain === 'number') { + if (plain <= min) { + const issue = TooSmallPlainParseIssue.numberGT(min, plain, path); + issues?.push(issue); + return issue; + } + return plain; + } + + const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); + issues?.push(issue); + return issue; + } + + public static parseNumberLT(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (typeof plain === 'number') { + if (plain >= max) { + const issue = TooBigPlainParseIssue.numberLT(max, plain, path); + issues?.push(issue); + return issue; + } + return plain; + } + + const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); + issues?.push(issue); + return issue; + } + + public static parseNumberLTE(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (typeof plain === 'number') { + if (plain > max) { + const issue = TooBigPlainParseIssue.numberLTE(max, plain, path); + issues?.push(issue); + return issue; + } + return plain; + } + + const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); + issues?.push(issue); + return issue; + } + + public static parseNumberExactly(plain: unknown, exactly: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { + if (typeof plain === 'number') { + if (plain !== exactly) { + const issue = plain > exactly + ? TooBigPlainParseIssue.numberExactly(exactly, plain, path) + : TooSmallPlainParseIssue.numberExactly(exactly, plain, path); + issues?.push(issue); + return issue; + } + return plain; + } + + const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); + issues?.push(issue); + return issue; + } +} diff --git a/src/Util/Plain/PlainParseHelper.ts b/src/Util/Plain/PlainParseHelper.ts index 148a946..cdecd39 100644 --- a/src/Util/Plain/PlainParseHelper.ts +++ b/src/Util/Plain/PlainParseHelper.ts @@ -1,7 +1,7 @@ import { AppErrorCode } from "../Error/AppError"; import { HObjectTypeMeta, type HObjectType, type PlainParsableHObjectType } from "../Feature"; import { ERR, type R } from "../Result"; -import { InvalidArrayElementsPlainParseIssue, InvalidHObjectPlainParseIssue as InvalidHObjectPlainParseIssue, InvalidStringPlainParseIssue, InvalidTypePlainParseIssue, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "./PlainParseIssue"; +import { InvalidHObjectPlainParseIssue, InvalidTypePlainParseIssue, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "./PlainParseIssue"; export const PlainParseError = 'core.plain.parse' as const; export type PlainParseError = typeof PlainParseError; @@ -14,12 +14,12 @@ const BigInt64Regex = /^-?\d{1,19}$/; export class PlainParseHelper { - public static HObjectParseErr(hObjectClass: HObjectType, issues: PlainParseIssue[]): R { + public static HObjectParseErr(hObjectClass: HObjectType, issues: PlainParseIssue[]): R { const meta = HObjectTypeMeta.extractFromClass(hObjectClass); return ERR(PlainParseError, AppErrorCode.BAD_REQUEST, new InvalidHObjectPlainParseIssue(meta, issues)); } - public static HObjectIsNotObjectParseErr(hcObjectClass: HObjectType, plain: unknown): R { + public static HObjectIsNotObjectParseErr(hcObjectClass: HObjectType, plain: unknown): R { return PlainParseHelper.HObjectParseErr(hcObjectClass, [new InvalidTypePlainParseIssue('object', typeof plain)]); } @@ -51,167 +51,6 @@ export class PlainParseHelper { return issue; } - public static parseNumber(plain: unknown, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { - if (typeof plain === 'number') { - return plain; - } - - const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); - if (issues) { - issues.push(issue); - } - return issue; - } - - public static parseNumberGTE(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { - if (typeof plain === 'number') { - if (plain < min) { - const issue = TooSmallPlainParseIssue.numberGTE(min, plain, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseNumberGT(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { - if (typeof plain === 'number') { - if (plain <= min) { - const issue = TooSmallPlainParseIssue.numberGT(min, plain, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseNumberLT(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { - if (typeof plain === 'number') { - if (plain >= max) { - const issue = TooBigPlainParseIssue.numberLT(max, plain, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseNumberLTE(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { - if (typeof plain === 'number') { - if (plain >= max) { - const issue = TooBigPlainParseIssue.numberLTE(max, plain, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseNumberExactly(plain: unknown, exactly: number, path?: string, issues?: PlainParseIssue[]): number | PlainParseIssue { - if (typeof plain === 'number') { - if (plain !== exactly) { - const issue = plain > exactly - ? TooBigPlainParseIssue.numberExactly(exactly, plain, path) - : TooSmallPlainParseIssue.numberExactly(exactly, plain, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('number', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseString(plain: unknown, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { - if (typeof plain === 'string') { - return plain; - } - - const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseStringRegex(plain: unknown, regex: RegExp, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { - if (typeof plain === 'string') { - if (!regex.test(plain)) { - const issue = InvalidStringPlainParseIssue.regex(regex, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseStringLength(plain: unknown, length: number, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { - if (typeof plain === 'string') { - if (plain.length !== length) { - const issue = plain.length > length - ? TooBigPlainParseIssue.stringLengthExactly(length, plain.length, path) - : TooSmallPlainParseIssue.stringLengthExactly(length, plain.length, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseStringLengthMin(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { - if (typeof plain === 'string') { - if (plain.length < min) { - const issue = TooSmallPlainParseIssue.stringLengthAtLeast(min, plain.length, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); - issues?.push(issue); - return issue; - } - - public static parseStringLengthMax(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { - if (typeof plain === 'string') { - if (plain.length > max) { - const issue = TooBigPlainParseIssue.stringLengthMax(max, plain.length, path); - issues?.push(issue); - return issue; - } - return plain; - } - - const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); - issues?.push(issue); - return issue; - } - public static parseBoolean(plain: unknown, path?: string, issues?: PlainParseIssue[]): boolean | PlainParseIssue { if (typeof plain === 'boolean') { return plain; @@ -230,96 +69,10 @@ export class PlainParseHelper { if (parsed.isError()) { const issue = parsed.e.data as InvalidHObjectPlainParseIssue; issue.path = path; - - if (issues) { - issues.push(issue); - } + issues?.push(issue); return issue; } return parsed.v; } - - public static parsePrimitiveArray(plain: unknown, parse: (v: unknown) => T | PlainParseIssue, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { - if (!Array.isArray(plain)) { - const issue = new InvalidTypePlainParseIssue('array', typeof plain, path); - if (issues) { - issues.push(issue); - } - - return issue; - } - - let localIssues: PlainParseIssue[] | null = null; - const parsedValues: T[] = []; - for (let i = 0; i < plain.length; i++) { - const parsed = parse(plain[i]); - - if (parsed instanceof PlainParseIssue) { - parsed.path = '' + i; - if (!localIssues) { - localIssues = [parsed]; - } else { - localIssues.push(parsed); - } - continue; - } - - if (!localIssues) { - parsedValues.push(parsed); - } - } - - if (localIssues) { - const issue = new InvalidArrayElementsPlainParseIssue(localIssues, path); - if (issues) { - issues.push(issue); - } - - return issue; - } - return parsedValues; - } - - public static parseHObjectArray(plain: unknown, objectClass: PlainParsableHObjectType, path?: string, issues?: PlainParseIssue[]): PlainParseIssue | T[] { - if (!Array.isArray(plain)) { - const issue = new InvalidTypePlainParseIssue('array', typeof plain, path); - if (issues) { - issues.push(issue); - } - - return issue; - } - - let localIssues: PlainParseIssue[] | null = null; - const parsedValues: T[] = []; - for (let i = 0; i < plain.length; i++) { - const parsed = objectClass.parse(plain[i]); - - if (parsed.isError()) { - const issue = parsed.e.data as InvalidHObjectPlainParseIssue; - issue.path = '' + i; - if (!localIssues) { - localIssues = [issue]; - } else { - localIssues.push(issue); - } - continue; - } - - if (!localIssues) { - parsedValues.push(parsed.v); - } - } - - if (localIssues) { - const issue = new InvalidArrayElementsPlainParseIssue(localIssues, path); - if (issues) { - issues.push(issue); - } - - return issue; - } - return parsedValues; - } } diff --git a/src/Util/Plain/PlainParseIssue.ts b/src/Util/Plain/PlainParseIssue.ts index 079df6b..ef6e8a9 100644 --- a/src/Util/Plain/PlainParseIssue.ts +++ b/src/Util/Plain/PlainParseIssue.ts @@ -7,6 +7,7 @@ export enum PlainParseIssueCode { invalid_string = 'invalid_string', too_small = 'too_small', too_big = 'too_big', + out_of_range = 'out_of_range', invalid_enum_value = 'invalid_enum_value', invalid_array_elements = 'invalid_array_elements', invalid_hobject = 'invalid_hobject', @@ -61,7 +62,7 @@ export abstract class PlainParseIssue implements JsonSerialize { } } -export type PlainParsePrimitiveType = 'string' | 'number' | 'bigint' | 'bigint_string' | 'boolean' | 'object' | 'array' | 'symbol' | 'undefined' | 'null' | 'function' | 'Date'; +export type PlainParsePrimitiveType = 'string' | 'number' | 'int' | 'uint' | 'bigint' | 'bigint_string' | 'boolean' | 'object' | 'array' | 'symbol' | 'undefined' | 'null' | 'function' | 'Date'; export class InvalidTypePlainParseIssue extends PlainParseIssue { public constructor( @@ -116,24 +117,79 @@ export class InvalidStringPlainParseIssue extends PlainParseIssue { } export enum ValueRangeSideMode { - bigint_exclusive = 'bigint_exclusive', - bigint_inclusive = 'bigint_inclusive', + bigint_exclusively = 'bigint_exclusively', + bigint_inclusively = 'bigint_inclusively', bigint_exactly = 'bigint_exactly', - number_exclusive = 'number_exclusive', - number_inclusive = 'number_inclusive', + number_exclusively = 'number_exclusively', + number_inclusively = 'number_inclusively', number_exactly = 'number_exactly', - array_exactly_size = 'array_exactly_size', - array_inclusive_size = 'array_inclusive_size', + array_size_exactly = 'array_size_exactly', + array_size_inclusively = 'array_size_inclusively', - string_exactly_len = 'string_exactly_len', - string_inclusive_len = 'string_inclusive_len', + string_len_exactly = 'string_len_exactly', + string_len_inclusively = 'string_len_inclusively', +} + +export class OutOfRangePlainParseIssue extends PlainParseIssue { + public constructor( + public min: number | bigint, + public max: number | bigint, + public valueType: "array" | "number", + public inclusively: boolean, + public current: number | bigint, + message: string, + path?: string + ) { + super(PlainParseIssueCode.out_of_range, message, path); + } + + public static arrayBetween(min: number, max: number, current: number, path?: string): OutOfRangePlainParseIssue { + const message = `Array size must be between ${min} and ${max} inclusively, current: ${current}`; + return new this(min, max, "array", true, current, message, path); + } + + public static stringLengthBetween(min: number, max: number, current: number, path?: string): OutOfRangePlainParseIssue { + const message = `String length must be between ${min} and ${max} inclusively, current: ${current}`; + return new this(min, max, "number", true, current, message, path); + } + + public static numberBetween(min: number, max: number, current: number, path?: string): OutOfRangePlainParseIssue { + const message = `Number must be between ${min} and ${max} inclusively, current: ${current}`; + return new this(min, max, "number", true, current, message, path); + } + + public static numberBetweenExclusively(min: number, max: number, current: number, path?: string): OutOfRangePlainParseIssue { + const message = `Number must be between ${min} and ${max} exclusively, current: ${current}`; + return new this(min, max, "number", false, current, message, path); + } + + + + public get i18n(): string { + return super.i18n + `.${this.valueType}.${this.inclusively ? "inclusively" : "exclusively"}`; + } + + public toJSON(): JsonObjectType { + return { + code: this.code, + message: this.message, + path: this.path, + i18n: this.i18n, + + min: typeof this.min === 'bigint' ? this.min.toString() : this.min, + max: typeof this.max === 'bigint' ? this.max.toString() : this.max, + valueType: this.valueType, + inclusively: this.inclusively, + current: typeof this.current === 'bigint' ? this.current.toString() : this.current, + }; + } } export class TooSmallPlainParseIssue extends PlainParseIssue { public constructor( - public minimum: number | bigint, + public min: number | bigint, public mode: ValueRangeSideMode, public current: number | bigint, message: string, @@ -144,32 +200,32 @@ export class TooSmallPlainParseIssue extends PlainParseIssue { public static arrayExactlySize(minimum: number, current: number, path?: string): TooSmallPlainParseIssue { const message = `Array must contain exactly ${minimum} element(s), current: ${current}`; - return new this(minimum, ValueRangeSideMode.array_exactly_size, current, message, path); + return new this(minimum, ValueRangeSideMode.array_size_exactly, current, message, path); } public static arrayAtLeastSize(minimum: number, current: number, path?: string): TooSmallPlainParseIssue { const message = `Array must contain at least ${minimum} element(s), current: ${current}`; - return new this(minimum, ValueRangeSideMode.array_inclusive_size, current, message, path); + return new this(minimum, ValueRangeSideMode.array_size_inclusively, current, message, path); } public static stringLengthExactly(minimum: number, current: number, path?: string): TooSmallPlainParseIssue { const message = `String must contain exactly ${minimum} character(s), current: ${current}`; - return new this(minimum, ValueRangeSideMode.string_exactly_len, current, message, path); + return new this(minimum, ValueRangeSideMode.string_len_exactly, current, message, path); } public static stringLengthAtLeast(minimum: number, current: number, path?: string): TooSmallPlainParseIssue { const message = `String must contain at least ${minimum} character(s), current: ${current}`; - return new this(minimum, ValueRangeSideMode.string_inclusive_len, current, message, path); + return new this(minimum, ValueRangeSideMode.string_len_inclusively, current, message, path); } public static numberGTE(minimum: number, current: number, path?: string): TooSmallPlainParseIssue { const message = `Number must be greater than or equal to ${minimum}, current: ${current}`; - return new this(minimum, ValueRangeSideMode.number_inclusive, current, message, path); + return new this(minimum, ValueRangeSideMode.number_inclusively, current, message, path); } public static numberGT(minimum: number, current: number, path?: string): TooSmallPlainParseIssue { const message = `Number must be greater than ${minimum}, current: ${current}`; - return new this(minimum, ValueRangeSideMode.number_exclusive, current, message, path); + return new this(minimum, ValueRangeSideMode.number_exclusively, current, message, path); } public static numberExactly(exactly: number, current: number, path?: string): TooSmallPlainParseIssue { @@ -179,7 +235,7 @@ export class TooSmallPlainParseIssue extends PlainParseIssue { public static bigintGTE(minimum: bigint, current: bigint, path?: string): TooSmallPlainParseIssue { const message = `Number must be greater than or equal to ${minimum}, current: ${current}`; - return new this(minimum, ValueRangeSideMode.bigint_inclusive, current, message, path); + return new this(minimum, ValueRangeSideMode.bigint_inclusively, current, message, path); } public get i18n(): string { @@ -193,7 +249,7 @@ export class TooSmallPlainParseIssue extends PlainParseIssue { path: this.path, i18n: this.i18n, - minimum: typeof this.minimum === 'bigint' ? this.minimum.toString() : this.minimum, + min: typeof this.min === 'bigint' ? this.min.toString() : this.min, mode: this.mode, current: typeof this.current === 'bigint' ? this.current.toString() : this.current, }; @@ -202,43 +258,44 @@ export class TooSmallPlainParseIssue extends PlainParseIssue { export class TooBigPlainParseIssue extends PlainParseIssue { public constructor( - public maximum: number, + public max: number, public mode: ValueRangeSideMode, public current: number, message: string, - path?: string + path?: string, + public minimum?: number, ) { super(PlainParseIssueCode.too_small, message, path); } public static arrayExactlySize(maximum: number, current: number, path?: string): TooBigPlainParseIssue { const message = `Array must contain exactly ${maximum} element(s), current: ${current}`; - return new this(maximum, ValueRangeSideMode.array_exactly_size, current, message, path); + return new this(maximum, ValueRangeSideMode.array_size_exactly, current, message, path); } public static arrayMaxSize(maximum: number, current: number, path?: string): TooBigPlainParseIssue { const message = `Array must contain maximum ${maximum} element(s), current: ${current}`; - return new this(maximum, ValueRangeSideMode.array_inclusive_size, current, message, path); + return new this(maximum, ValueRangeSideMode.array_size_inclusively, current, message, path); } public static stringLengthExactly(maximum: number, current: number, path?: string): TooBigPlainParseIssue { const message = `String must contain exactly ${maximum} character(s), current: ${current}`; - return new this(maximum, ValueRangeSideMode.string_exactly_len, current, message, path); + return new this(maximum, ValueRangeSideMode.string_len_exactly, current, message, path); } public static stringLengthMax(maximum: number, current: number, path?: string): TooBigPlainParseIssue { const message = `String must contain maximum ${maximum} character(s), current: ${current}`; - return new this(maximum, ValueRangeSideMode.string_inclusive_len, current, message, path); + return new this(maximum, ValueRangeSideMode.string_len_inclusively, current, message, path); } public static numberLTE(maximum: number, current: number, path?: string): TooBigPlainParseIssue { const message = `Number must be less than or equal to ${maximum}, current: ${current}`; - return new this(maximum, ValueRangeSideMode.number_inclusive, current, message, path); + return new this(maximum, ValueRangeSideMode.number_inclusively, current, message, path); } public static numberLT(maximum: number, current: number, path?: string): TooBigPlainParseIssue { const message = `Number must be less than ${maximum}, current: ${current}`; - return new this(maximum, ValueRangeSideMode.number_exclusive, current, message, path); + return new this(maximum, ValueRangeSideMode.number_exclusively, current, message, path); } public static numberExactly(exactly: number, current: number, path?: string): TooBigPlainParseIssue { @@ -253,7 +310,7 @@ export class TooBigPlainParseIssue extends PlainParseIssue { path: this.path, i18n: this.i18n, - maximum: this.maximum, + max: this.max, mode: this.mode, current: this.current, }; @@ -310,7 +367,7 @@ export class InvalidHObjectPlainParseIssue extends PlainParseIssue { public issues: PlainParseIssue[], path?: string ) { - super(PlainParseIssueCode.invalid_hobject, `Invalid object of type: ${typeMeta.typeId}`, path); + super(PlainParseIssueCode.invalid_hobject, `Invalid plain object to parse to HObject: ${typeMeta.typeId}`, path); } public toJSON(): JsonObjectType & { typeId: string; } { diff --git a/src/Util/Plain/StringPlainParseHelper.ts b/src/Util/Plain/StringPlainParseHelper.ts new file mode 100644 index 0000000..ae88c4f --- /dev/null +++ b/src/Util/Plain/StringPlainParseHelper.ts @@ -0,0 +1,93 @@ +import { InvalidStringPlainParseIssue, InvalidTypePlainParseIssue, OutOfRangePlainParseIssue, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "./PlainParseIssue"; + +export class StringPlainParseHelper { + + public static parseString(plain: unknown, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { + if (typeof plain === 'string') { + return plain; + } + + const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); + issues?.push(issue); + return issue; + } + + public static parseStringRegex(plain: unknown, regex: RegExp, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { + if (typeof plain !== 'string') { + const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); + issues?.push(issue); + return issue; + } + + if (!regex.test(plain)) { + const issue = InvalidStringPlainParseIssue.regex(regex, path); + issues?.push(issue); + return issue; + } + return plain; + } + + public static parseStringLength(plain: unknown, length: number, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { + if (typeof plain !== 'string') { + const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length !== length) { + const issue = plain.length > length + ? TooBigPlainParseIssue.stringLengthExactly(length, plain.length, path) + : TooSmallPlainParseIssue.stringLengthExactly(length, plain.length, path); + issues?.push(issue); + return issue; + } + + return plain; + } + + public static parseStringLengthMin(plain: unknown, min: number, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { + if (typeof plain !== 'string') { + const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length < min) { + const issue = TooSmallPlainParseIssue.stringLengthAtLeast(min, plain.length, path); + issues?.push(issue); + return issue; + } + return plain; + } + + public static parseStringLengthMax(plain: unknown, max: number, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { + if (typeof plain !== 'string') { + const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length > max) { + const issue = TooBigPlainParseIssue.stringLengthMax(max, plain.length, path); + issues?.push(issue); + return issue; + } + return plain; + } + + public static parseStringLengthBetween(plain: unknown, min: number, max: number, path?: string, issues?: PlainParseIssue[]): string | PlainParseIssue { + if (typeof plain !== 'string') { + const issue = new InvalidTypePlainParseIssue('string', typeof plain, path); + issues?.push(issue); + return issue; + } + + if (plain.length < min || plain.length > max) { + const issue = OutOfRangePlainParseIssue.stringLengthBetween(min, max, plain.length, path); + issues?.push(issue); + return issue; + } + + return plain; + } +} diff --git a/src/Util/Plain/index.ts b/src/Util/Plain/index.ts index b3c8194..7b888fa 100644 --- a/src/Util/Plain/index.ts +++ b/src/Util/Plain/index.ts @@ -1,3 +1,7 @@ -export * from './PlainParseHelper'; export * from './PlainParseIssue'; -export * from './types'; \ No newline at end of file +export * from './types'; +export * from './PlainParseHelper'; +export * from './IntegerPlainParseHelper'; +export * from './ArrayPlainParseHelper'; +export * from './StringPlainParseHelper'; +export * from './NumberPlainParseHelper'; \ No newline at end of file diff --git a/src/Util/Plain/types.ts b/src/Util/Plain/types.ts index 2b3f6df..c288e2b 100644 --- a/src/Util/Plain/types.ts +++ b/src/Util/Plain/types.ts @@ -26,7 +26,7 @@ export namespace v { export type gt = NumberValueRuleTag<'float_gt'>; export type lt = NumberValueRuleTag<'float_lt'>; - export type between_exclusive = NumberValueRuleTag<'float_between_exclusive'>; + export type between_exclusively = NumberValueRuleTag<'float_between_exclusive'>; } export type int = NumberValueRuleTag<'int'>; @@ -35,7 +35,7 @@ export namespace v { export type max = NumberValueRuleTag<'int_max'>; export type between = NumberValueRuleTag<'int_between'>; - export type between_exclusive = NumberValueRuleTag<'int_between_exclusive'>; + export type between_exclusively = NumberValueRuleTag<'int_between_exclusive'>; export type gt = NumberValueRuleTag<'int_gt'>; export type lt = NumberValueRuleTag<'int_lt'>; } @@ -46,7 +46,7 @@ export namespace v { export type max = number & ValueRuleTagBase<'uint_max'>; export type between = NumberValueRuleTag<'uint_between'>; - export type between_exclusive = NumberValueRuleTag<'uint_between_exclusive'>; + export type between_exclusively = NumberValueRuleTag<'uint_between_exclusive'>; export type gt = NumberValueRuleTag<'uint_gt'>; export type lt = NumberValueRuleTag<'uint_lt'>; } diff --git a/test/helper/TestDto.ts b/test/helper/TestDto.ts index cd395ec..5d8d444 100644 --- a/test/helper/TestDto.ts +++ b/test/helper/TestDto.ts @@ -1,21 +1,24 @@ import { UIntValue, Dto, type JsonObjectType, - type PlainParsableHObjectType, type R, type PlainParseError, PlainParseHelper, - InvalidTypePlainParseIssue, OK, HObjectTypeMeta, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue, + type AnyDto, + type DtoType, + ArrayPlainParseHelper, + StringPlainParseHelper, + NumberPlainParseHelper, } from "@"; import type { v } from "@/Util/Plain/types"; export class TestValueObject extends UIntValue { } -class OtherTestDto extends Dto { +export class OtherTestDto extends Dto { public primitiveField!: v.int.between<10, 100>; // generate constructor in AOT @@ -26,18 +29,17 @@ class OtherTestDto extends Dto { this.primitiveField = primitiveField; } - - public static parse(this: PlainParsableHObjectType, plain: unknown): R { + public static parse(this: DtoType, plain: unknown): R { // constant check part if (typeof plain !== 'object') { - return PlainParseHelper.HObjectParseErr(this, [new InvalidTypePlainParseIssue('object', typeof plain)]); + return PlainParseHelper.HObjectIsNotObjectParseErr(this, plain); } const plainObj = plain as Record; const issues: PlainParseIssue[] = []; // end constant check part - const primitiveField = PlainParseHelper.parseNumber(plainObj.primitiveField, 'primitiveField', issues); + const primitiveField = NumberPlainParseHelper.parseNumber(plainObj.primitiveField, 'primitiveField', issues); if (!(primitiveField instanceof PlainParseIssue)) { if (primitiveField < 10) { issues.push(TooSmallPlainParseIssue.numberGTE(10, primitiveField)); @@ -64,7 +66,7 @@ class OtherTestDto extends Dto { } } -export class TestDto extends Dto { +export class TestDto extends Dto { public static HOBJ_META = HObjectTypeMeta.application('core', 'core', 'dto', 'TestDto', TestDto); public constructor( @@ -84,20 +86,20 @@ export class TestDto extends Dto { super(); } - public static parse(this: PlainParsableHObjectType, plain: unknown): R { + public static parse(this: DtoType, plain: unknown): R { // constant check part if (typeof plain !== 'object') { - return PlainParseHelper.HObjectIsNotObjectParseErr(TestDto as any, plain); + return PlainParseHelper.HObjectIsNotObjectParseErr(TestDto, plain); } const plainObj = plain as Record; const issues: PlainParseIssue[] = []; // end constant check part - const stringField = PlainParseHelper.parseString(plainObj.stringField, 'stringField', issues); + const stringField = StringPlainParseHelper.parseString(plainObj.stringField, 'stringField', issues); const bigIntField = PlainParseHelper.parseBigInt64(plainObj.bigIntField, 'bigIntField', issues); - const numberField = PlainParseHelper.parseNumberGTE(plainObj.numberField, 0, 'numberField', issues); - const numberArrayField = PlainParseHelper.parsePrimitiveArray(plainObj.numberArrayField, PlainParseHelper.parseNumber, 'numberArrayField', issues); + const numberField = NumberPlainParseHelper.parseNumberGTE(plainObj.numberField, 0, 'numberField', issues); + const numberArrayField = ArrayPlainParseHelper.parsePrimitiveArray(plainObj.numberArrayField, NumberPlainParseHelper.parseNumber, 'numberArrayField', issues); const booleanField = PlainParseHelper.parseBoolean(plainObj.booleanField, 'booleanField', issues); let valueObjectField; @@ -107,12 +109,12 @@ export class TestDto extends Dto { let optionalStringField; if (plainObj.optionalStringField !== undefined) { - optionalStringField = PlainParseHelper.parseString(plainObj.optionalStringField, 'optionalStringField', issues); + optionalStringField = StringPlainParseHelper.parseString(plainObj.optionalStringField, 'optionalStringField', issues); } let optionalValueObjectArrayField; if (plainObj.optionalValueObjectArrayField !== undefined) { - optionalValueObjectArrayField = PlainParseHelper.parseHObjectArray(plainObj.optionalValueObjectArrayField, TestValueObject, 'optionalValueObjectArrayField', issues); + optionalValueObjectArrayField = ArrayPlainParseHelper.parseHObjectArray(plainObj.optionalValueObjectArrayField, TestValueObject, 'optionalValueObjectArrayField', issues); } let optionalDtoField; @@ -122,24 +124,25 @@ export class TestDto extends Dto { let optionalDtoArrayField; if (plainObj.optionalDtoArrayField !== undefined) { - optionalDtoArrayField = PlainParseHelper.parseHObjectArray(plainObj.optionalDtoArrayField, OtherTestDto, 'optionalDtoArrayField', issues); + optionalDtoArrayField = ArrayPlainParseHelper.parseHObjectArray(plainObj.optionalDtoArrayField, OtherTestDto, 'optionalDtoArrayField', issues); } if (issues.length > 0) { - return PlainParseHelper.HObjectParseErr(this, issues); + return PlainParseHelper.HObjectParseErr(TestDto, issues); } - return OK(new this( - stringField, - bigIntField, - numberField, - numberArrayField, - booleanField, - valueObjectField, - optionalValueObjectArrayField, - optionalDtoField, - optionalDtoArrayField - )); + return OK(new TestDto( + stringField as any, + bigIntField as any, + numberField as any, + numberArrayField as any, + booleanField as any, + valueObjectField as any, + optionalStringField as any, + optionalValueObjectArrayField as any, + optionalDtoField as any, + optionalDtoArrayField as any + )) as any; } @@ -150,10 +153,20 @@ export class TestDto extends Dto { numberField: this.numberField, numberArrayField: this.numberArrayField, booleanField: this.booleanField, + optionalStringField: this.optionalStringField, optionalValueObjectField: this.optionalValueObjectField?.toJSON(), optionalValueObjectArrayField: this.optionalValueObjectArrayField?.map(v => v.toJSON()), optionalDtoField: this.optionalDtoField?.toJSON(), optionalDtoArrayField: this.optionalDtoArrayField?.map((v) => v.toJSON()), }; } -} \ No newline at end of file +} + +class AOTTestDto extends Dto { + public stringField!: string; + public numberField!: v.uint; +} + +// Uncomment to check return types +//const aotCSReturnType = AOTTestDto.cs({stringField: "test", numberField: 10}); +//const aotParseReturnType = AOTTestDto.parse({stringField: "test", numberField: 10}); \ No newline at end of file diff --git a/test/unit/Util/Dto.test.ts b/test/unit/Util/Dto.test.ts index 3eb9031..d73cf9d 100644 --- a/test/unit/Util/Dto.test.ts +++ b/test/unit/Util/Dto.test.ts @@ -1,86 +1,25 @@ -import { Dto, UIntValue, type JsonObjectType } from '@'; -import path from 'path'; +import { OtherTestDto, TestDto, TestValueObject } from "@test/helper/TestDto"; /** * @group unit */ -class TestValueObject extends UIntValue { } - -class OtherTestDto extends Dto { - public constructor( - public primitiveField?: number - ) { - super(); - } - - public toJSON(): JsonObjectType { - return { - primitiveField: this.primitiveField, - } as JsonObjectType; - } -} - -class TestDto extends Dto { - public constructor( - public bigIntField: bigint, - public bigIntArrayField: bigint[], - public valueObjectField?: TestValueObject, - public valueObjectArrayField?: TestValueObject[], - public dtoField?: OtherTestDto, - public dtoArrayField?: OtherTestDto[], - public primitiveField?: number, - ) { - super(); - } - - - public toJSON(): JsonObjectType { - return { - primitiveField: this.primitiveField, - bigIntField: this.bigIntField.toString(), - bigIntArrayField: this.bigIntArrayField.map(v => v.toString()), - valueObjectField: this.valueObjectField, - valueObjectArrayField: this.valueObjectArrayField, - dtoField: this.dtoField?.toJSON(), - dtoArrayField: this.dtoArrayField?.map((v) => v.toJSON()), - } as JsonObjectType; - } -} - -describe.skip(path.basename(__filename, '.test.ts'), () => { - test('toJSON', () => { +describe("Dto", () => { + test("toJSON", () => { const dto = TestDto.cs({ - primitiveField: 1, + booleanField: true, + numberArrayField: [1, 2], + numberField: 1, + stringField: "test", + optionalDtoArrayField: [OtherTestDto.cs({ primitiveField: 1 })], + optionalDtoField: OtherTestDto.cs({ primitiveField: 2 }), bigIntField: 1000n, - bigIntArrayField: [2000n, 2001n], - valueObjectField: TestValueObject.cs(2), - valueObjectArrayField: [TestValueObject.cs(3), TestValueObject.cs(4)], - dtoField: OtherTestDto.cs({ primitiveField: 5 }), - dtoArrayField: [OtherTestDto.cs({ primitiveField: 6 }), OtherTestDto.cs({ primitiveField: 7 })], + optionalValueObjectField: TestValueObject.cs(2), + optionalValueObjectArrayField: [TestValueObject.cs(3), TestValueObject.cs(4)], }); const current = dto.toJSON(); - const expected = { - bigIntArrayField: ['2000', '2001'], - bigIntField: '1000', - dtoArrayField: [ - { - primitiveField: 6, - }, - { - primitiveField: 7, - }, - ], - dtoField: { - primitiveField: 5, - }, - primitiveField: 1, - valueObjectArrayField: [3, 4], - valueObjectField: 2, - }; - - expect(current).toEqual(expected); + expect(current).toMatchSnapshot(); }); }); diff --git a/test/unit/Util/Plain/ObjectPropertyTypes.test.ts b/test/unit/Util/Plain/ObjectPropertyTypes.test.ts index 8dcf4eb..ad23c76 100644 --- a/test/unit/Util/Plain/ObjectPropertyTypes.test.ts +++ b/test/unit/Util/Plain/ObjectPropertyTypes.test.ts @@ -18,7 +18,7 @@ class Super { public intLtField: v.int.lt<-10>, public intGtField: v.int.gt<100>, - public intBetweenExclusiveField: v.int.between_exclusive<-10, 100>, + public intBetweenExclusiveField: v.int.between_exclusively<-10, 100>, public uintField: v.uint, @@ -28,7 +28,7 @@ class Super { public uintLtField: v.uint.lt<10>, public uintGtField: v.uint.gt<100>, - public uintBetweenExclusiveField: v.uint.between_exclusive<10, 100>, + public uintBetweenExclusiveField: v.uint.between_exclusively<10, 100>, public floatField: v.float, public floatMinField: v.float.min<-1.5>, @@ -37,7 +37,7 @@ class Super { public floatLtField: v.float.lt<-1.5>, public floatGtField: v.float.gt<2.5>, - public floatBetweenExclusiveField: v.float.between_exclusive<-1.5, 2.5>, + public floatBetweenExclusiveField: v.float.between_exclusively<-1.5, 2.5>, public intArray10: v.int.between<10, 100>[] & v.items.exactly<10>, public intArrayMin: v.int.between<10, 100>[] & v.items.min<10>, diff --git a/test/unit/Util/Plain/PlainParseHelper.test.ts b/test/unit/Util/Plain/PlainParseHelper.test.ts index 2125394..4656480 100644 --- a/test/unit/Util/Plain/PlainParseHelper.test.ts +++ b/test/unit/Util/Plain/PlainParseHelper.test.ts @@ -1,4 +1,4 @@ -import { AppErrorCode, InvalidArrayElementsPlainParseIssue, InvalidHObjectPlainParseIssue, InvalidTypePlainParseIssue, PlainParseError, PlainParseHelper, TooBigPlainParseIssue, TooSmallPlainParseIssue, type JsonObjectType } from '@'; +import { AppErrorCode, ArrayPlainParseHelper, InvalidArrayElementsPlainParseIssue, InvalidHObjectPlainParseIssue, InvalidTypePlainParseIssue, NumberPlainParseHelper, PlainParseError, PlainParseHelper, StringPlainParseHelper, TooBigPlainParseIssue, TooSmallPlainParseIssue, type JsonObjectType } from '@'; import { TestDto } from '@test/helper/TestDto'; import path from 'node:path'; @@ -38,7 +38,7 @@ describe(path.basename(__filename, '.test.ts'), () => { test('when valid should return parsed', () => { const plain = 1000; - const current = PlainParseHelper.parseNumber(plain); + const current = NumberPlainParseHelper.parseNumber(plain); expect(current).toBe(1000); }); @@ -46,7 +46,7 @@ describe(path.basename(__filename, '.test.ts'), () => { test('when invalid should return issue', () => { const plain = 'bad1000'; - const current = PlainParseHelper.parseNumber(plain); + const current = NumberPlainParseHelper.parseNumber(plain); expect(current).toEqual(new InvalidTypePlainParseIssue('number', 'string')); }); @@ -74,7 +74,7 @@ describe(path.basename(__filename, '.test.ts'), () => { test('when valid should return parsed', () => { const plain = 'good'; - const current = PlainParseHelper.parseString(plain); + const current = StringPlainParseHelper.parseString(plain); expect(current).toBe('good'); }); @@ -82,7 +82,7 @@ describe(path.basename(__filename, '.test.ts'), () => { test('when invalid should return issue', () => { const plain = 1000; - const current = PlainParseHelper.parseString(plain); + const current = StringPlainParseHelper.parseString(plain); expect(current).toEqual(new InvalidTypePlainParseIssue('string', 'number')); }); @@ -95,7 +95,7 @@ describe(path.basename(__filename, '.test.ts'), () => { { plain: 'good2', expected: TooBigPlainParseIssue.stringLengthExactly(4, 5) }, { plain: 1000, expected: new InvalidTypePlainParseIssue('string', 'number') } ])('when input is $plain, it should return $expected', ({ plain, expected }) => { - const current = PlainParseHelper.parseStringLength(plain, 4); + const current = StringPlainParseHelper.parseStringLength(plain, 4); expect(current).toEqual(expected); }); }); @@ -107,7 +107,7 @@ describe(path.basename(__filename, '.test.ts'), () => { { plain: 'good2', expected: 'good2' }, { plain: 1000, expected: new InvalidTypePlainParseIssue('string', 'number') } ])('when input is $plain, it should return $expected', ({ plain, expected }) => { - const current = PlainParseHelper.parseStringLengthMin(plain, 4); + const current = StringPlainParseHelper.parseStringLengthMin(plain, 4); expect(current).toEqual(expected); }); }); @@ -119,7 +119,7 @@ describe(path.basename(__filename, '.test.ts'), () => { { plain: 'good2', expected: TooBigPlainParseIssue.stringLengthMax(4, 5) }, { plain: 1000, expected: new InvalidTypePlainParseIssue('string', 'number') } ])('when input is $plain, it should return $expected', ({ plain, expected }) => { - const current = PlainParseHelper.parseStringLengthMax(plain, 4); + const current = StringPlainParseHelper.parseStringLengthMax(plain, 4); expect(current).toEqual(expected); }); }); @@ -168,7 +168,7 @@ describe(path.basename(__filename, '.test.ts'), () => { booleanField: true, }; - const current = PlainParseHelper.parseHObjectArray([plain], TestDto); + const current = ArrayPlainParseHelper.parseHObjectArray([plain], TestDto); const expected = TestDto.cs({ stringField: 'test', @@ -183,7 +183,7 @@ describe(path.basename(__filename, '.test.ts'), () => { test('when invalid should return issue', () => { const plain = 1000; - const current = PlainParseHelper.parseHObjectArray([plain], TestDto); + const current = ArrayPlainParseHelper.parseHObjectArray([plain], TestDto); const expected = new InvalidArrayElementsPlainParseIssue([ new InvalidHObjectPlainParseIssue(TestDto.HOBJ_META, [ diff --git a/test/unit/Util/__snapshots__/Dto.test.ts.snap b/test/unit/Util/__snapshots__/Dto.test.ts.snap new file mode 100644 index 0000000..01252b7 --- /dev/null +++ b/test/unit/Util/__snapshots__/Dto.test.ts.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Dto toJSON 1`] = ` +{ + "bigIntField": "1000", + "booleanField": true, + "numberArrayField": [ + 1, + 2, + ], + "numberField": 1, + "optionalDtoArrayField": [ + { + "primitiveField": 1, + }, + ], + "optionalDtoField": { + "primitiveField": 2, + }, + "optionalStringField": undefined, + "optionalValueObjectArrayField": [ + 3, + 4, + ], + "optionalValueObjectField": 2, + "stringField": "test", +} +`;