Skip to content

Commit cc0b7da

Browse files
authored
Merge pull request #13 from AthennaIO/develop
feat(validator): add custom exists rule
2 parents 4c1c956 + 8f489a4 commit cc0b7da

File tree

16 files changed

+666
-149
lines changed

16 files changed

+666
-149
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@athenna/validator",
3-
"version": "5.2.0",
3+
"version": "5.3.0",
44
"description": "The Athenna validation solution. Built on top of VineJS.",
55
"license": "MIT",
66
"author": "João Lenon <lenon@athenna.io>",

src/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import vine, {
2121
VineLiteral,
2222
VineBoolean,
2323
VineAccepted,
24-
SimpleErrorReporter
24+
SimpleErrorReporter,
25+
SimpleMessagesProvider
2526
} from '@vinejs/vine'
2627

2728
export * from '#src/types'
@@ -32,7 +33,10 @@ export * from '#src/validator/ValidatorImpl'
3233
export * from '#src/providers/ValidatorProvider'
3334
export * from '#src/exceptions/ValidationException'
3435

36+
const v = vine
37+
3538
export {
39+
v,
3640
vine,
3741
Vine,
3842
VineAny,
@@ -47,5 +51,6 @@ export {
4751
VineLiteral,
4852
VineBoolean,
4953
VineAccepted,
50-
SimpleErrorReporter
54+
SimpleErrorReporter,
55+
SimpleMessagesProvider
5156
}

src/providers/ValidatorProvider.ts

Lines changed: 7 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -9,53 +9,18 @@
99

1010
import { sep } from 'node:path'
1111
import { Config } from '@athenna/config'
12-
import { Validate, SimpleErrorReporter } from '#src'
13-
import { Is, Exec, Module, Path } from '@athenna/common'
12+
import { SimpleErrorReporter } from '#src'
13+
import { Exec, Module, Path } from '@athenna/common'
1414
import { Annotation, ServiceProvider } from '@athenna/ioc'
1515
import { ValidatorImpl } from '#src/validator/ValidatorImpl'
16+
import type { UniqueOptions, ExistsOptions } from '#src/types'
17+
import { CustomValidations } from '#src/validator/CustomValidations'
1618
import { ValidationException } from '#src/exceptions/ValidationException'
1719

18-
type UniqueOptions = {
19-
/**
20-
* The table where the database will lookup for the data.
21-
*/
22-
table: string
23-
24-
/**
25-
* The column name in database. If not defined, the name
26-
* of the field in the schema will be used.
27-
*
28-
* @default 'fieldNameInYourSchema'
29-
*/
30-
column?: string
31-
32-
/**
33-
* Use the max field to stablish a max limit for your validation.
34-
* In some cases in your database you might have a max of 10 tuples
35-
* with the same data. Use this option to validate that the number
36-
* of fields registered in database cannot be bigger than the number
37-
* defined on this option.
38-
*
39-
* @example
40-
* ```ts
41-
* const schema = this.validator.object({
42-
* name: this.validator.string().unique({ table: 'users', max: 10 })
43-
* })
44-
*
45-
* const data = { name: 'lenon' }
46-
*
47-
* // Will throw if there are 10 users with name `lenon`
48-
* // created in database
49-
* await this.validator.validate({ schema: this.schema, data })
50-
* ```
51-
* @default undefined
52-
*/
53-
max?: number
54-
}
55-
5620
declare module '@vinejs/vine' {
5721
interface VineString {
5822
unique(options: UniqueOptions): this
23+
exists(options: ExistsOptions): this
5924
}
6025
}
6126

@@ -81,44 +46,8 @@ export class ValidatorProvider extends ServiceProvider {
8146
return
8247
}
8348

84-
const DB = ioc.safeUse('Athenna/Core/Database')
85-
86-
Validate.extend().string('unique', async (value, options, field) => {
87-
/**
88-
* We do not want to deal with non-string
89-
* values. The "string" rule will handle the
90-
* the validation.
91-
*/
92-
if (!Is.String(value)) {
93-
return
94-
}
95-
96-
if (!options.column) {
97-
options.column = field.name as string
98-
}
99-
100-
if (options.max) {
101-
const rows = await DB.table(options.table)
102-
.select(options.column)
103-
.where(options.column, value)
104-
.findMany()
105-
106-
if (rows.length > options.max) {
107-
field.report('The {{ field }} field is not unique', 'unique', field)
108-
}
109-
110-
return
111-
}
112-
113-
const existsRow = await DB.table(options.table)
114-
.select(options.column)
115-
.where(options.column, value)
116-
.exists()
117-
118-
if (existsRow) {
119-
field.report('The {{ field }} field is not unique', 'unique', field)
120-
}
121-
})
49+
CustomValidations.registerUnique()
50+
CustomValidations.registerExists()
12251
}
12352

12453
public async registerValidators() {

src/types/ExtendReturnType.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { ExtendHandlerType } from '#src/types'
2+
import type { ValidatorImpl } from '#src/validator/ValidatorImpl'
3+
4+
export type ExtendReturnType = {
5+
/**
6+
* Extend error messages of all your validations. This method
7+
* doesn't save past extends, which means that if you call
8+
* it twice, only the second one will be respected.
9+
*
10+
* ```ts
11+
* Validate.extend().messages({
12+
* // Applicable for all fields
13+
* 'required': 'The {{ field }} field is required',
14+
* 'string': 'The value of {{ field }} field must be a string',
15+
* 'email': 'The value is not a valid email address',
16+
*
17+
* // Error message only for the username field
18+
* 'username.required': 'Please choose a username for your account'
19+
* })
20+
* ```
21+
*/
22+
messages: (messages: Record<string, string>) => ValidatorImpl
23+
accepted: (name: string, handler: ExtendHandlerType) => ValidatorImpl
24+
date: (name: string, handler: ExtendHandlerType) => ValidatorImpl
25+
record: (name: string, handler: ExtendHandlerType) => ValidatorImpl
26+
tuple: (name: string, handler: ExtendHandlerType) => ValidatorImpl
27+
literal: (name: string, handler: ExtendHandlerType) => ValidatorImpl
28+
array: (name: string, handler: ExtendHandlerType) => ValidatorImpl
29+
any: (name: string, handler: ExtendHandlerType) => ValidatorImpl
30+
string: (name: string, handler: ExtendHandlerType) => ValidatorImpl
31+
number: (name: string, handler: ExtendHandlerType) => ValidatorImpl
32+
enum: (name: string, handler: ExtendHandlerType) => ValidatorImpl
33+
boolean: (name: string, handler: ExtendHandlerType) => ValidatorImpl
34+
object: (name: string, handler: ExtendHandlerType) => ValidatorImpl
35+
}

src/types/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,7 @@ import type {
1414
} from '@vinejs/vine/types'
1515

1616
export * from '#src/types/ExtendHandler'
17+
export * from '#src/types/ExtendReturnType'
18+
export * from '#src/types/validators/ExistsOptions'
19+
export * from '#src/types/validators/UniqueOptions'
1720
export type { FieldContext, SchemaTypes, ErrorReporterContract }
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @athenna/validator
3+
*
4+
* (c) João Lenon <lenon@athenna.io>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
export type ExistsOptions = {
11+
/**
12+
* The table where the database will lookup for the data.
13+
*/
14+
table: string
15+
16+
/**
17+
* The column name in database. If not defined, the name
18+
* of the field in the schema will be used.
19+
*
20+
* @default 'fieldNameInYourSchema'
21+
*/
22+
column?: string
23+
24+
/**
25+
* Use the min field to stablish a min limit for your validation.
26+
* In some cases in your database you might have a min of 10 tuples
27+
* with the same data. Use this option to validate that the number
28+
* of fields registered in database needs to be the same or bigger
29+
* than the number defined on this option.
30+
*
31+
* @example
32+
* ```ts
33+
* const schema = this.validator.object({
34+
* name: this.validator.string().exists({ table: 'users', min: 10 })
35+
* })
36+
*
37+
* const data = { name: 'lenon' }
38+
*
39+
* // Will throw if there aren't 10 users with name `lenon`
40+
* // created in database
41+
* await this.validator.validate({ schema: this.schema, data })
42+
* ```
43+
* @default undefined
44+
*/
45+
min?: number
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* @athenna/validator
3+
*
4+
* (c) João Lenon <lenon@athenna.io>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
export type UniqueOptions = {
11+
/**
12+
* The table where the database will lookup for the data.
13+
*/
14+
table: string
15+
16+
/**
17+
* The column name in database. If not defined, the name
18+
* of the field in the schema will be used.
19+
*
20+
* @default 'fieldNameInYourSchema'
21+
*/
22+
column?: string
23+
24+
/**
25+
* Use the max field to stablish a max limit for your validation.
26+
* In some cases in your database you might have a max of 10 tuples
27+
* with the same data. Use this option to validate that the number
28+
* of fields registered in database cannot be bigger than the number
29+
* defined on this option.
30+
*
31+
* @example
32+
* ```ts
33+
* const schema = this.validator.object({
34+
* name: this.validator.string().unique({ table: 'users', max: 10 })
35+
* })
36+
*
37+
* const data = { name: 'lenon' }
38+
*
39+
* // Will throw if there are 10 users with name `lenon`
40+
* // created in database
41+
* await this.validator.validate({ schema: this.schema, data })
42+
* ```
43+
* @default undefined
44+
*/
45+
max?: number
46+
}

src/validator/BaseValidator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* file that was distributed with this source code.
88
*/
99

10+
import { v } from '#src'
1011
import type { SchemaTypes } from '#src/types'
1112
import { Validate } from '#src/facades/Validate'
1213

@@ -17,6 +18,6 @@ export abstract class BaseValidator {
1718
public abstract handle(data: any): Promise<void>
1819

1920
public async validate(data: any) {
20-
return this.validator.validate({ schema: this.schema, data })
21+
return v.validate({ schema: this.schema, data })
2122
}
2223
}

0 commit comments

Comments
 (0)