Skip to content

Commit

Permalink
feat: add first set of date validation rules
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Nov 21, 2023
1 parent 628b4c7 commit c893f10
Show file tree
Hide file tree
Showing 12 changed files with 3,250 additions and 33 deletions.
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export { VineArray } from './src/schema/array/main.js'
export { VineValidator } from './src/vine/validator.js'
export { VineString } from './src/schema/string/main.js'
export { VineNumber } from './src/schema/number/main.js'
export { VineDate } from './src/schema/date/main.js'
export { VineRecord } from './src/schema/record/main.js'
export { VineObject } from './src/schema/object/main.js'
export { VineLiteral } from './src/schema/literal/main.js'
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@types/validator": "^13.11.6",
"@vinejs/compiler": "^2.3.0",
"camelcase": "^8.0.0",
"dayjs": "^1.11.10",
"dlv": "^1.1.3",
"normalize-url": "^8.0.0",
"validator": "^13.11.0"
Expand Down
15 changes: 15 additions & 0 deletions src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ export const messages = {
'union': 'Invalid value provided for {{ field }} field',
'unionGroup': 'Invalid value provided for {{ field }} field',
'unionOfTypes': 'Invalid value provided for {{ field }} field',

'date': 'The {{ field }} field must be a datetime value',
'date.equals': 'The {{ field }} field must be a date equal to {{ expectedValue }}',
'date.after': 'The {{ field }} field must be a date after {{ expectedValue }}',
'date.before': 'The {{ field }} field must be a date before {{ expectedValue }}',
'date.afterOrEqual': 'The {{ field }} field must be a date after or equal to {{ expectedValue }}',
'date.beforeOrEqual':
'The {{ field }} field must be a date before or equal to {{ expectedValue }}',

'date.sameAs': 'The {{ field }} field and {{ otherField }} field must be the same',
'date.notSameAs': 'The {{ field }} field and {{ otherField }} field must be different',
'date.afterField': 'The {{ field }} field must be a date after {{ otherField }}',
'date.afterOrSameAs': 'The {{ field }} field must be a date after or same as {{ otherField }}',
'date.beforeField': 'The {{ field }} field must be a date before {{ otherField }}',
'date.beforeOrSameAs': 'The {{ field }} field must be a date before or same as {{ otherField }}',
}

/**
Expand Down
10 changes: 9 additions & 1 deletion src/schema/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { group } from './object/group_builder.js'
import { VineNativeEnum } from './enum/native_enum.js'
import { VineUnionOfTypes } from './union_of_types/main.js'
import { OTYPE, COTYPE, IS_OF_TYPE, UNIQUE_NAME } from '../symbols.js'
import type { EnumLike, FieldContext, SchemaTypes } from '../types.js'
import type { DateFieldOptions, EnumLike, FieldContext, SchemaTypes } from '../types.js'
import { VineDate } from './date/main.js'

/**
* Schema builder exposes methods to construct a Vine schema. You may
Expand Down Expand Up @@ -71,6 +72,13 @@ export class SchemaBuilder extends Macroable {
return new VineNumber(options)
}

/**
* Define a datetime value
*/
date(options?: DateFieldOptions) {
return new VineDate(options)
}

/**
* Define a schema type in which the input value
* matches the pre-defined value
Expand Down
230 changes: 230 additions & 0 deletions src/schema/date/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* vinejs
*
* (c) VineJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import dayjs from 'dayjs'
import { BaseLiteralType } from '../base/literal.js'
import { IS_OF_TYPE, UNIQUE_NAME } from '../../symbols.js'
import {
dateRule,
afterRule,
beforeRule,
sameAsRule,
equalsRule,
notSameAsRule,
afterFieldRule,
beforeFieldRule,
afterOrEqualRule,
afterOrSameAsRule,
beforeOrEqualRule,
beforeOrSameAsRule,
DEFAULT_DATE_FORMATS,
} from './rules.js'
import type {
Validation,
FieldOptions,
FieldContext,
DateFieldOptions,
DateEqualsOptions,
} from '../../types.js'

/**
* VineDate represents a Date object created by parsing a
* string or number value as a date.
*/
export class VineDate extends BaseLiteralType<Date, Date> {
/**
* Available VineDate rules
*/
static rules = {
equals: equalsRule,
after: afterRule,
afterOrEqual: afterOrEqualRule,
before: beforeRule,
beforeOrEqual: beforeOrEqualRule,
sameAs: sameAsRule,
notSameAs: notSameAsRule,
afterField: afterFieldRule,
afterOrSameAs: afterOrSameAsRule,
beforeField: beforeFieldRule,
beforeOrSameAs: beforeOrSameAsRule,
};

/**
* The property must be implemented for "unionOfTypes"
*/
[UNIQUE_NAME] = 'vine.date';

/**
* Checks if the value is of date type. The method must be
* implemented for "unionOfTypes"
*/
[IS_OF_TYPE] = (value: unknown) => {
if (typeof value !== 'string') {
return false
}

return dayjs(value, this.options.formats || DEFAULT_DATE_FORMATS, true).isValid()
}

protected declare options: FieldOptions & DateFieldOptions

constructor(options?: Partial<FieldOptions> & DateFieldOptions, validations?: Validation<any>[]) {
super(options, validations || [dateRule(options || {})])
}

/**
* The equals rule compares the input value to be same
* as the expected value.
*
* By default, the comparions of day, month and years are performed.
*/
equals(
expectedValue: string | ((field: FieldContext) => string),
options?: DateEqualsOptions
): this {
return this.use(equalsRule({ expectedValue, ...options }))
}

/**
* The after rule compares the input value to be after
* the expected value.
*
* By default, the comparions of day, month and years are performed.
*/
after(
expectedValue:
| 'today'
| 'tomorrow'
| (string & { _?: never })
| ((field: FieldContext) => string),
options?: DateEqualsOptions
): this {
return this.use(afterRule({ expectedValue, ...options }))
}

/**
* The after or equal rule compares the input value to be
* after or equal to the expected value.
*
* By default, the comparions of day, month and years are performed.
*/
afterOrEqual(
expectedValue:
| 'today'
| 'tomorrow'
| (string & { _?: never })
| ((field: FieldContext) => string),
options?: DateEqualsOptions
): this {
return this.use(afterOrEqualRule({ expectedValue, ...options }))
}

/**
* The before rule compares the input value to be before
* the expected value.
*
* By default, the comparions of day, month and years are performed.
*/
before(
expectedValue:
| 'today'
| 'yesterday'
| (string & { _?: never })
| ((field: FieldContext) => string),
options?: DateEqualsOptions
): this {
return this.use(beforeRule({ expectedValue, ...options }))
}

/**
* The before rule compares the input value to be before
* the expected value.
*
* By default, the comparions of day, month and years are performed.
*/
beforeOrEqual(
expectedValue:
| 'today'
| 'yesterday'
| (string & { _?: never })
| ((field: FieldContext) => string),
options?: DateEqualsOptions
): this {
return this.use(beforeOrEqualRule({ expectedValue, ...options }))
}

/**
* The sameAs rule expects the input value to be same
* as the value of the other field.
*
* By default, the comparions of day, month and years are performed
*/
sameAs(otherField: string, options?: DateEqualsOptions): this {
return this.use(sameAsRule({ otherField, ...options }))
}

/**
* The notSameAs rule expects the input value to be different
* from the other field's value
*
* By default, the comparions of day, month and years are performed
*/

notSameAs(otherField: string, options?: DateEqualsOptions): this {
return this.use(notSameAsRule({ otherField, ...options }))
}

/**
* The afterField rule expects the input value to be after
* the other field's value.
*
* By default, the comparions of day, month and years are performed
*/
afterField(otherField: string, options?: DateEqualsOptions): this {
return this.use(afterFieldRule({ otherField, ...options }))
}

/**
* The afterOrSameAs rule expects the input value to be after
* or equal to the other field's value.
*
* By default, the comparions of day, month and years are performed
*/
afterOrSameAs(otherField: string, options?: DateEqualsOptions): this {
return this.use(afterOrSameAsRule({ otherField, ...options }))
}

/**
* The beforeField rule expects the input value to be before
* the other field's value.
*
* By default, the comparions of day, month and years are performed
*/
beforeField(otherField: string, options?: DateEqualsOptions): this {
return this.use(beforeFieldRule({ otherField, ...options }))
}

/**
* The beforeOrSameAs rule expects the input value to be before
* or same as the other field's value.
*
* By default, the comparions of day, month and years are performed
*/
beforeOrSameAs(otherField: string, options?: DateEqualsOptions): this {
return this.use(beforeOrSameAsRule({ otherField, ...options }))
}

/**
* Clones the VineDate schema type. The applied options
* and validations are copied to the new instance
*/
clone(): this {
return new VineDate(this.cloneOptions(), this.cloneValidations()) as this
}
}
Loading

0 comments on commit c893f10

Please sign in to comment.