Skip to content

Commit

Permalink
feat: add support for getErrorMessages and getCheckResult
Browse files Browse the repository at this point in the history
  • Loading branch information
simonguo committed Apr 10, 2024
1 parent 53a0b37 commit e7b94ea
Show file tree
Hide file tree
Showing 5 changed files with 377 additions and 87 deletions.
25 changes: 18 additions & 7 deletions src/MixedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
AsyncValidCallbackType,
RuleType,
ErrorMessageType,
TypeName
TypeName,
PlainObject
} from './types';
import {
checkRequired,
Expand All @@ -25,6 +26,9 @@ type ProxyOptions = {

export const schemaSpecKey = 'objectTypeSchemaSpec';

/**
* Get the field type from the schema object
*/
export function getFieldType(schemaSpec: any, fieldName: string, nestedObject?: boolean) {
if (nestedObject) {
const namePath = fieldName.split('.').join(`.${schemaSpecKey}.`);
Expand All @@ -33,8 +37,15 @@ export function getFieldType(schemaSpec: any, fieldName: string, nestedObject?:
return schemaSpec?.[fieldName];
}

/**
* Get the field value from the data object
*/
export function getFieldValue(data: PlainObject, fieldName: string, nestedObject?: boolean) {
return nestedObject ? get(data, fieldName) : data?.[fieldName];
}

export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L = any> {
readonly typeName?: string;
readonly $typeName?: string;
protected required = false;
protected requiredMessage: E | string = '';
protected trim = false;
Expand All @@ -43,7 +54,7 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
protected priorityRules: RuleType<ValueType, DataType, E | string>[] = [];
protected fieldLabel?: string;

schemaSpec: SchemaDeclaration<DataType, E>;
$schemaSpec: SchemaDeclaration<DataType, E>;
value: any;
locale: L & MixedTypeLocale;

Expand All @@ -52,12 +63,12 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
proxyOptions: ProxyOptions = {};

constructor(name?: TypeName) {
this.typeName = name;
this.$typeName = name;
this.locale = Object.assign(name ? locales[name] : {}, locales.mixed) as L & MixedTypeLocale;
}

setSchemaOptions(schemaSpec: SchemaDeclaration<DataType, E>, value: any) {
this.schemaSpec = schemaSpec;
this.$schemaSpec = schemaSpec;
this.value = value;
}

Expand Down Expand Up @@ -194,7 +205,7 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
when(condition: (schemaSpec: SchemaDeclaration<DataType, E>) => MixedType) {
this.addRule(
(value, data, fieldName) => {
return condition(this.schemaSpec).check(value, data, fieldName);
return condition(this.$schemaSpec).check(value, data, fieldName);
},
undefined,
true
Expand All @@ -215,7 +226,7 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
*/
equalTo(fieldName: string, errorMessage: E | string = this.locale.equalTo) {
const errorMessageFunc = () => {
const type = getFieldType(this.schemaSpec, fieldName, true);
const type = getFieldType(this.$schemaSpec, fieldName, true);
return formatErrorMessage(errorMessage, { toFieldName: type?.fieldLabel || fieldName });
};

Expand Down
104 changes: 75 additions & 29 deletions src/Schema.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { SchemaDeclaration, SchemaCheckResult, CheckResult, PlainObject } from './types';
import { MixedType, getFieldType } from './MixedType';
import get from './utils/get';
import set from './utils/set';
import isEmpty from './utils/isEmpty';
import { MixedType, getFieldType, getFieldValue } from './MixedType';
import { set, get, isEmpty } from './utils';

interface CheckOptions {
/**
Expand All @@ -11,27 +9,40 @@ interface CheckOptions {
nestedObject?: boolean;
}

/**
* Get the field value from the data object
*/
function getFieldValue(data: PlainObject, fieldName: string, nestedObject?: boolean) {
return nestedObject ? get(data, fieldName) : data?.[fieldName];
function pathTransform(path: string) {
const arr = path.split('.');

if (arr.length === 1) {
return path;
}

return path
.split('.')
.map((item, index) => {
if (index === 0) {
return item;
}

// Check if the item is a number, e.g. `list.0`
return /^\d+$/.test(item) ? `array.${item}` : `object.${item}`;
})
.join('.');
}

export class Schema<DataType = any, ErrorMsgType = string> {
readonly spec: SchemaDeclaration<DataType, ErrorMsgType>;
readonly $spec: SchemaDeclaration<DataType, ErrorMsgType>;
private data: PlainObject;
private state: SchemaCheckResult<DataType, ErrorMsgType> = {};
private checkResult: SchemaCheckResult<DataType, ErrorMsgType> = {};

constructor(schema: SchemaDeclaration<DataType, ErrorMsgType>) {
this.spec = schema;
this.$spec = schema;
}

private getFieldType<T extends keyof DataType>(
fieldName: T,
nestedObject?: boolean
): SchemaDeclaration<DataType, ErrorMsgType>[T] {
return getFieldType(this.spec, fieldName as string, nestedObject);
return getFieldType(this.$spec, fieldName as string, nestedObject);
}

private setFieldCheckResult(
Expand All @@ -41,34 +52,69 @@ export class Schema<DataType = any, ErrorMsgType = string> {
) {
if (nestedObject) {
const namePath = fieldName.split('.').join('.object.');
set(this.state, namePath, checkResult);
set(this.checkResult, namePath, checkResult);

return;
}

this.state[fieldName as string] = checkResult;
}

getState() {
return this.state;
this.checkResult[fieldName as string] = checkResult;
}

getKeys() {
return Object.keys(this.spec);
}

setSchemaOptionsForAllType(data: PlainObject) {
private setSchemaOptionsForAllType(data: PlainObject) {
if (data === this.data) {
return;
}

Object.entries(this.spec).forEach(([key, type]) => {
(type as MixedType).setSchemaOptions(this.spec as any, data?.[key]);
Object.entries(this.$spec).forEach(([key, type]) => {
(type as MixedType).setSchemaOptions(this.$spec as any, data?.[key]);
});

this.data = data;
}

/**
* Get the check result of the schema
* @returns CheckResult<ErrorMsgType | string>
*/
getCheckResult(path?: string): CheckResult<ErrorMsgType | string> {
if (path) {
return get(this.checkResult, pathTransform(path)) || { hasError: false };
}

return this.checkResult;
}

/**
* Get the error messages of the schema
*
*/
getErrorMessages(path?: string): (string | ErrorMsgType)[] {
let messages: (string | ErrorMsgType)[] = [];

if (path) {
const { errorMessage, object, array } = get(this.checkResult, pathTransform(path)) || {};

if (errorMessage) {
messages = [errorMessage];
} else if (object) {
messages = Object.keys(object).map(key => object[key]?.errorMessage);
} else if (array) {
messages = array.map(item => item?.errorMessage);
}
} else {
messages = Object.keys(this.checkResult).map(key => this.checkResult[key]?.errorMessage);
}

return messages.filter(Boolean);
}

/**
* Get all the keys of the schema
*/
getKeys() {
return Object.keys(this.$spec);
}

checkForField<T extends keyof DataType>(
fieldName: T,
data: DataType,
Expand Down Expand Up @@ -153,7 +199,7 @@ export class Schema<DataType = any, ErrorMsgType = string> {

check<T extends keyof DataType>(data: DataType) {
const checkResult: SchemaCheckResult<DataType, ErrorMsgType> = {};
Object.keys(this.spec).forEach(key => {
Object.keys(this.$spec).forEach(key => {
if (typeof data === 'object') {
checkResult[key] = this.checkForField(key as T, data);
}
Expand All @@ -167,7 +213,7 @@ export class Schema<DataType = any, ErrorMsgType = string> {
const promises: Promise<CheckResult<ErrorMsgType | string>>[] = [];
const keys: string[] = [];

Object.keys(this.spec).forEach((key: string) => {
Object.keys(this.$spec).forEach((key: string) => {
keys.push(key);
promises.push(this.checkForFieldAsync(key as T, data));
});
Expand All @@ -193,7 +239,7 @@ SchemaModel.combine = function combine<DataType = any, ErrorMsgType = string>(
) {
return new Schema<DataType, ErrorMsgType>(
specs
.map(model => model.spec)
.map(model => model.$spec)
.reduce((accumulator, currentValue) => Object.assign(accumulator, currentValue), {} as any)
);
};
30 changes: 15 additions & 15 deletions test/MixedTypeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('#MixedType', () => {
});
});

it('Should check if two fields are the same and the filed value is not root', () => {
it('Should check if two fields are the same and the field value is not root', () => {
const schema = SchemaModel({
a: StringType().isRequired(),
b: StringType()
Expand Down Expand Up @@ -411,7 +411,7 @@ describe('#MixedType', () => {
schema.checkForField('password', { password: '123', confirmPassword: '13' })
).to.deep.equal({ hasError: false });

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
password: { hasError: false },
confirmPassword: {
hasError: true,
Expand All @@ -429,7 +429,7 @@ describe('#MixedType', () => {
confirmPassword: { hasError: false }
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
password: { hasError: false },
confirmPassword: { hasError: false }
});
Expand All @@ -446,7 +446,7 @@ describe('#MixedType', () => {
errorMessage: 'a is a required field'
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
a: { hasError: true, errorMessage: 'a is a required field' }
});
});
Expand All @@ -463,7 +463,7 @@ describe('#MixedType', () => {
hasError: false
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
a: { hasError: false },
b: { object: { c: { hasError: true, errorMessage: 'b.c is a required field' } } }
});
Expand All @@ -481,7 +481,7 @@ describe('#MixedType', () => {
hasError: false
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
a: { hasError: false },
b: { hasError: true, errorMessage: 'b is a required field' },
d: { hasError: true, errorMessage: 'd is a required field' }
Expand All @@ -498,13 +498,13 @@ describe('#MixedType', () => {
hasError: false
});

expect(schema.getState()).to.deep.equal({ a: { hasError: false } });
expect(schema.getCheckResult()).to.deep.equal({ a: { hasError: false } });

expect(schema.checkForField('a', { a: 'a', b: 1 })).to.deep.equal({
hasError: false
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
a: { hasError: false },
b: {
hasError: true,
Expand Down Expand Up @@ -536,7 +536,7 @@ describe('#MixedType', () => {
return result;
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
password: { hasError: false },
confirmPassword: {
hasError: true,
Expand All @@ -558,7 +558,7 @@ describe('#MixedType', () => {
});
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
password: { hasError: false },
confirmPassword: { hasError: false }
});
Expand All @@ -577,7 +577,7 @@ describe('#MixedType', () => {
});
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
a: { hasError: true, errorMessage: 'a is a required field' }
});
});
Expand All @@ -596,7 +596,7 @@ describe('#MixedType', () => {
});
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
a: { hasError: false },
b: { object: { c: { hasError: true, errorMessage: 'b.c is a required field' } } }
});
Expand All @@ -614,7 +614,7 @@ describe('#MixedType', () => {
expect(result).to.deep.equal({ hasError: false });
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
a: { hasError: false },
b: { hasError: true, errorMessage: 'b is a required field' },
d: { hasError: true, errorMessage: 'd is a required field' }
Expand All @@ -631,13 +631,13 @@ describe('#MixedType', () => {
expect(result).to.deep.equal({ hasError: false });
});

expect(schema.getState()).to.deep.equal({ a: { hasError: false } });
expect(schema.getCheckResult()).to.deep.equal({ a: { hasError: false } });

await schema.checkForFieldAsync('a', { a: 'a', b: 1 }).then(result => {
expect(result).to.deep.equal({ hasError: false });
});

expect(schema.getState()).to.deep.equal({
expect(schema.getCheckResult()).to.deep.equal({
a: { hasError: false },
b: {
hasError: true,
Expand Down
Loading

0 comments on commit e7b94ea

Please sign in to comment.