Skip to content

Commit

Permalink
Merge pull request #13 from AthennaIO/develop
Browse files Browse the repository at this point in the history
feat(validator): add custom exists rule
  • Loading branch information
jlenon7 authored Dec 30, 2024
2 parents 4c1c956 + 8f489a4 commit cc0b7da
Show file tree
Hide file tree
Showing 16 changed files with 666 additions and 149 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@athenna/validator",
"version": "5.2.0",
"version": "5.3.0",
"description": "The Athenna validation solution. Built on top of VineJS.",
"license": "MIT",
"author": "João Lenon <[email protected]>",
Expand Down
9 changes: 7 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import vine, {
VineLiteral,
VineBoolean,
VineAccepted,
SimpleErrorReporter
SimpleErrorReporter,
SimpleMessagesProvider
} from '@vinejs/vine'

export * from '#src/types'
Expand All @@ -32,7 +33,10 @@ export * from '#src/validator/ValidatorImpl'
export * from '#src/providers/ValidatorProvider'
export * from '#src/exceptions/ValidationException'

const v = vine

export {
v,
vine,
Vine,
VineAny,
Expand All @@ -47,5 +51,6 @@ export {
VineLiteral,
VineBoolean,
VineAccepted,
SimpleErrorReporter
SimpleErrorReporter,
SimpleMessagesProvider
}
85 changes: 7 additions & 78 deletions src/providers/ValidatorProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,18 @@

import { sep } from 'node:path'
import { Config } from '@athenna/config'
import { Validate, SimpleErrorReporter } from '#src'
import { Is, Exec, Module, Path } from '@athenna/common'
import { SimpleErrorReporter } from '#src'
import { Exec, Module, Path } from '@athenna/common'
import { Annotation, ServiceProvider } from '@athenna/ioc'
import { ValidatorImpl } from '#src/validator/ValidatorImpl'
import type { UniqueOptions, ExistsOptions } from '#src/types'
import { CustomValidations } from '#src/validator/CustomValidations'
import { ValidationException } from '#src/exceptions/ValidationException'

type UniqueOptions = {
/**
* The table where the database will lookup for the data.
*/
table: string

/**
* The column name in database. If not defined, the name
* of the field in the schema will be used.
*
* @default 'fieldNameInYourSchema'
*/
column?: string

/**
* Use the max field to stablish a max limit for your validation.
* In some cases in your database you might have a max of 10 tuples
* with the same data. Use this option to validate that the number
* of fields registered in database cannot be bigger than the number
* defined on this option.
*
* @example
* ```ts
* const schema = this.validator.object({
* name: this.validator.string().unique({ table: 'users', max: 10 })
* })
*
* const data = { name: 'lenon' }
*
* // Will throw if there are 10 users with name `lenon`
* // created in database
* await this.validator.validate({ schema: this.schema, data })
* ```
* @default undefined
*/
max?: number
}

declare module '@vinejs/vine' {
interface VineString {
unique(options: UniqueOptions): this
exists(options: ExistsOptions): this
}
}

Expand All @@ -81,44 +46,8 @@ export class ValidatorProvider extends ServiceProvider {
return
}

const DB = ioc.safeUse('Athenna/Core/Database')

Validate.extend().string('unique', async (value, options, field) => {
/**
* We do not want to deal with non-string
* values. The "string" rule will handle the
* the validation.
*/
if (!Is.String(value)) {
return
}

if (!options.column) {
options.column = field.name as string
}

if (options.max) {
const rows = await DB.table(options.table)
.select(options.column)
.where(options.column, value)
.findMany()

if (rows.length > options.max) {
field.report('The {{ field }} field is not unique', 'unique', field)
}

return
}

const existsRow = await DB.table(options.table)
.select(options.column)
.where(options.column, value)
.exists()

if (existsRow) {
field.report('The {{ field }} field is not unique', 'unique', field)
}
})
CustomValidations.registerUnique()
CustomValidations.registerExists()
}

public async registerValidators() {
Expand Down
35 changes: 35 additions & 0 deletions src/types/ExtendReturnType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ExtendHandlerType } from '#src/types'
import type { ValidatorImpl } from '#src/validator/ValidatorImpl'

export type ExtendReturnType = {
/**
* Extend error messages of all your validations. This method
* doesn't save past extends, which means that if you call
* it twice, only the second one will be respected.
*
* ```ts
* Validate.extend().messages({
* // Applicable for all fields
* 'required': 'The {{ field }} field is required',
* 'string': 'The value of {{ field }} field must be a string',
* 'email': 'The value is not a valid email address',
*
* // Error message only for the username field
* 'username.required': 'Please choose a username for your account'
* })
* ```
*/
messages: (messages: Record<string, string>) => ValidatorImpl
accepted: (name: string, handler: ExtendHandlerType) => ValidatorImpl
date: (name: string, handler: ExtendHandlerType) => ValidatorImpl
record: (name: string, handler: ExtendHandlerType) => ValidatorImpl
tuple: (name: string, handler: ExtendHandlerType) => ValidatorImpl
literal: (name: string, handler: ExtendHandlerType) => ValidatorImpl
array: (name: string, handler: ExtendHandlerType) => ValidatorImpl
any: (name: string, handler: ExtendHandlerType) => ValidatorImpl
string: (name: string, handler: ExtendHandlerType) => ValidatorImpl
number: (name: string, handler: ExtendHandlerType) => ValidatorImpl
enum: (name: string, handler: ExtendHandlerType) => ValidatorImpl
boolean: (name: string, handler: ExtendHandlerType) => ValidatorImpl
object: (name: string, handler: ExtendHandlerType) => ValidatorImpl
}
3 changes: 3 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ import type {
} from '@vinejs/vine/types'

export * from '#src/types/ExtendHandler'
export * from '#src/types/ExtendReturnType'
export * from '#src/types/validators/ExistsOptions'
export * from '#src/types/validators/UniqueOptions'
export type { FieldContext, SchemaTypes, ErrorReporterContract }
46 changes: 46 additions & 0 deletions src/types/validators/ExistsOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* @athenna/validator
*
* (c) João Lenon <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export type ExistsOptions = {
/**
* The table where the database will lookup for the data.
*/
table: string

/**
* The column name in database. If not defined, the name
* of the field in the schema will be used.
*
* @default 'fieldNameInYourSchema'
*/
column?: string

/**
* Use the min field to stablish a min limit for your validation.
* In some cases in your database you might have a min of 10 tuples
* with the same data. Use this option to validate that the number
* of fields registered in database needs to be the same or bigger
* than the number defined on this option.
*
* @example
* ```ts
* const schema = this.validator.object({
* name: this.validator.string().exists({ table: 'users', min: 10 })
* })
*
* const data = { name: 'lenon' }
*
* // Will throw if there aren't 10 users with name `lenon`
* // created in database
* await this.validator.validate({ schema: this.schema, data })
* ```
* @default undefined
*/
min?: number
}
46 changes: 46 additions & 0 deletions src/types/validators/UniqueOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* @athenna/validator
*
* (c) João Lenon <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

export type UniqueOptions = {
/**
* The table where the database will lookup for the data.
*/
table: string

/**
* The column name in database. If not defined, the name
* of the field in the schema will be used.
*
* @default 'fieldNameInYourSchema'
*/
column?: string

/**
* Use the max field to stablish a max limit for your validation.
* In some cases in your database you might have a max of 10 tuples
* with the same data. Use this option to validate that the number
* of fields registered in database cannot be bigger than the number
* defined on this option.
*
* @example
* ```ts
* const schema = this.validator.object({
* name: this.validator.string().unique({ table: 'users', max: 10 })
* })
*
* const data = { name: 'lenon' }
*
* // Will throw if there are 10 users with name `lenon`
* // created in database
* await this.validator.validate({ schema: this.schema, data })
* ```
* @default undefined
*/
max?: number
}
3 changes: 2 additions & 1 deletion src/validator/BaseValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* file that was distributed with this source code.
*/

import { v } from '#src'
import type { SchemaTypes } from '#src/types'
import { Validate } from '#src/facades/Validate'

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

public async validate(data: any) {
return this.validator.validate({ schema: this.schema, data })
return v.validate({ schema: this.schema, data })
}
}
Loading

0 comments on commit cc0b7da

Please sign in to comment.