Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for label method #77

Merged
merged 2 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 48 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Schema for data modeling & validation
- [`when(condition: (schemaSpec: SchemaDeclaration<DataType, ErrorMsgType>) => Type)`](#whencondition-schemaspec-schemadeclarationdatatype-errormsgtype--type)
- [`check(value: ValueType, data?: DataType):CheckResult`](#checkvalue-valuetype-data-datatypecheckresult)
- [`checkAsync(value: ValueType, data?: DataType):Promise<CheckResult>`](#checkasyncvalue-valuetype-data-datatypepromisecheckresult)
- [`label(label: string)`](#labellabel-string)
- [StringType(errorMessage?: string)](#stringtypeerrormessage-string)
- [`isEmail(errorMessage?: string)`](#isemailerrormessage-string)
- [`isURL(errorMessage?: string)`](#isurlerrormessage-string)
Expand Down Expand Up @@ -445,37 +446,49 @@ MixedType().addAsyncRule((value, data) => {

#### `when(condition: (schemaSpec: SchemaDeclaration<DataType, ErrorMsgType>) => Type)`

Define data verification rules based on conditions.
Conditional validation, the return value is a new type.

```js
```ts
const model = SchemaModel({
age: NumberType().min(18, 'error'),
contact: MixedType().when(schema => {
const checkResult = schema.age.check();
return checkResult.hasError
? StringType().isRequired('Please provide contact information')
: StringType();
option: StringType().isOneOf(['a', 'b', 'other']),
other: StringType().when(schema => {
const { value } = schema.option;
return value === 'other' ? StringType().isRequired('Other required') : StringType();
})
});

/**
{
age: { hasError: false },
contact: { hasError: false }
{
option: { hasError: false },
other: { hasError: false }
}
*/
model.check({ age: 18, contact: '' });
model.check({ option: 'a', other: '' });

/*
{
age: { hasError: true, errorMessage: 'error' },
contact: {
hasError: true,
errorMessage: 'Please provide contact information'
}
option: { hasError: false },
other: { hasError: true, errorMessage: 'Other required' }
}
*/
model.check({ age: 17, contact: '' });
model.check({ option: 'other', other: '' });
```

Check whether a field passes the validation to determine the validation rules of another field.

```js
const model = SchemaModel({
password: StringType().isRequired('Password required'),
confirmPassword: StringType().when(schema => {
const { hasError } = schema.password.check();
return hasError
? StringType()
: StringType().addRule(
value => value === schema.password.value,
'The passwords are inconsistent twice'
);
})
});
```

#### `check(value: ValueType, data?: DataType):CheckResult`
Expand Down Expand Up @@ -515,6 +528,23 @@ type.checkAsync(1).then(checkResult => {
});
```

#### `label(label: string)`

Overrides the key name in error messages.

```js
MixedType().label('Username');
```

Eg:

```js
SchemaModel({
first_name: StringType().label('First name'),
age: NumberType().label('Age')
});
```

### StringType(errorMessage?: string)

Define a string type. Supports all the same methods as [MixedType](#mixedtype).
Expand Down
17 changes: 15 additions & 2 deletions src/MixedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
protected emptyAllowed = false;
protected rules: RuleType<ValueType, DataType, E | string>[] = [];
protected priorityRules: RuleType<ValueType, DataType, E | string>[] = [];
protected fieldLabel?: string;

schemaSpec: SchemaDeclaration<DataType, E>;
value: any;
Expand All @@ -43,7 +44,9 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
if (this.required && !checkRequired(value, this.trim, this.emptyAllowed)) {
return {
hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage, { name: fieldName })
errorMessage: formatErrorMessage(this.requiredMessage, {
name: this.fieldLabel || fieldName
})
};
}

Expand All @@ -70,7 +73,9 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
if (this.required && !checkRequired(value, this.trim, this.emptyAllowed)) {
return Promise.resolve({
hasError: true,
errorMessage: formatErrorMessage(this.requiredMessage, { name: fieldName })
errorMessage: formatErrorMessage(this.requiredMessage, {
name: this.fieldLabel || fieldName
})
});
}

Expand Down Expand Up @@ -160,6 +165,14 @@ export class MixedType<ValueType = any, DataType = any, E = ErrorMessageType, L
);
return this;
}

/**
* Overrides the key name in error messages.
*/
label(label: string) {
this.fieldLabel = label;
return this;
}
}

export default function getMixedType<DataType = any, E = ErrorMessageType>() {
Expand Down
125 changes: 100 additions & 25 deletions test/MixedTypeSpec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const chai = require('chai');
const schema = require('../src');
import chai, { expect } from 'chai';
import * as schema from '../src';

chai.should();

const { StringType, SchemaModel, NumberType, ArrayType, MixedType } = schema;

describe('#MixedType', () => {
Expand Down Expand Up @@ -364,42 +365,103 @@ describe('#MixedType', () => {

const checkResult1 = model.check({ field1: 20, field2: 2 });

checkResult1.field1.hasError.should.equal(false);
checkResult1.field2.hasError.should.equal(false);
expect(checkResult1).to.deep.equal({
field1: { hasError: false },
field2: { hasError: false }
});

const checkResult2 = model.check({ field1: 1, field2: 1 });

checkResult2.field1.hasError.should.equal(true);
checkResult2.field2.hasError.should.equal(true);
checkResult2.field2.errorMessage.should.equal('error1');
expect(checkResult2).to.deep.equal({
field1: { hasError: true, errorMessage: 'field1 must be greater than or equal to 10' },
field2: { hasError: true, errorMessage: 'error1' }
});

const checkResult3 = model.check({ field1: 10, field2: 1 });

checkResult3.field1.hasError.should.equal(false);
checkResult3.field2.hasError.should.equal(true);
checkResult3.field2.errorMessage.should.equal('error2');
expect(checkResult3).to.deep.equal({
field1: { hasError: false },
field2: { hasError: true, errorMessage: 'error2' }
});

const checkResult4 = model.checkForField('field2', { field1: 20, field2: 1 });
checkResult4.errorMessage.should.equal('error2');

expect(checkResult4).to.deep.equal({ hasError: true, errorMessage: 'error2' });

const checkResult5 = model.checkForField('field2', { field1: 9, field2: 1 });
checkResult5.errorMessage.should.equal('error1');

expect(checkResult5).to.deep.equal({ hasError: true, errorMessage: 'error1' });
});

it('Should be high priority even if it is empty', () => {
it('Should type be changed by condition', () => {
const model = SchemaModel({
age: NumberType().min(18, 'error1'),
contact: StringType().when(schema => {
const checkResult = schema.age.check();
return checkResult.hasError ? StringType().isRequired('error2') : StringType();
option: StringType().isOneOf(['a', 'b', 'other']),
other: StringType().when(schema => {
const { value } = schema.option;
return value === 'other' ? StringType().isRequired('Other required') : StringType();
})
});

const checkResult = model.check({ age: 17, contact: '' });
const checkResult = model.check({ option: 'a', other: '' });

expect(checkResult).to.deep.equal({
option: { hasError: false },
other: { hasError: false }
});

checkResult.age.hasError.should.equal(true);
checkResult.contact.hasError.should.equal(true);
checkResult.contact.errorMessage.should.equal('error2');
const checkResult2 = model.check({ option: 'other', other: '' });

expect(checkResult2).to.deep.equal({
option: { hasError: false },
other: { hasError: true, errorMessage: 'Other required' }
});
});

it('Should type be changed by condition', () => {
const model = SchemaModel({
password: StringType().isRequired('Password required'),
confirmPassword: StringType().when(schema => {
const { hasError } = schema.password.check();
return hasError
? StringType()
: StringType()
.addRule(
value => value === schema.password.value,
'The passwords are inconsistent twice'
)
.isRequired()
.label('Confirm password');
})
});

const checkResult = model.check({ password: '', confirmPassword: '123' });

expect(checkResult).to.deep.equal({
password: { hasError: true, errorMessage: 'Password required' },
confirmPassword: { hasError: false }
});

const checkResult2 = model.check({ password: '123', confirmPassword: '123' });

expect(checkResult2).to.deep.equal({
password: { hasError: false },
confirmPassword: { hasError: false }
});

const checkResult3 = model.check({ password: '123', confirmPassword: '1234' });

expect(checkResult3).to.deep.equal({
password: { hasError: false },
confirmPassword: { hasError: true, errorMessage: 'The passwords are inconsistent twice' }
});

const checkResult4 = model.check({ password: '123', confirmPassword: '' });

expect(checkResult4).to.deep.equal({
password: { hasError: false },
confirmPassword: { hasError: true, errorMessage: 'Confirm password is a required field' }
});
});

it('should error when an async rule is executed by the sync validator', () => {
Expand Down Expand Up @@ -450,14 +512,27 @@ describe('#MixedType', () => {
.isRequired('error2');
setTimeout(() => {
try {
chai.expect(called).to.eq(false);
chai.expect(type.check('').hasError).to.eq(true);
chai.expect(type.check('1').hasError).to.eq(true);
chai.expect(type.check(1).hasError).to.eq(false);
expect(called).to.eq(false);
expect(type.check('').hasError).to.eq(true);
expect(type.check('1').hasError).to.eq(true);
expect(type.check(1).hasError).to.eq(false);

done();
} catch (e) {
done(e);
}
}, 100);
});

it('Should use label to override the field name in the error message', () => {
const schema = SchemaModel({
first_name: StringType().label('First Name').isRequired(),
age: NumberType().label('Age').isRequired()
});

expect(schema.check({})).to.deep.equal({
first_name: { hasError: true, errorMessage: 'First Name is a required field' },
age: { hasError: true, errorMessage: 'Age is a required field' }
});
});
});
Loading