Skip to content

Commit

Permalink
feat: update validator composable to handle additional validation types
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanvier committed Oct 15, 2024
1 parent c3bad26 commit b9d0b61
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 33 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@wgr-sa/nuxt-form",
"description": "Form builder for Nuxt",
"version": "0.8.9",
"version": "0.9.0",
"repository": "https://github.com/WGR-SA/nuxt-form.git",
"author": "jeanvier",
"license": "MIT",
Expand Down
17 changes: 10 additions & 7 deletions playground/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
// watchEffect(() => {
// if testForm.value.isReady()
// testForm.value.submit()
import { max } from 'class-validator';
// })
</script>

Expand All @@ -12,15 +15,15 @@
ref="testForm"
action="https://httpbin.org/post"
>
<FormInput
<!-- <FormInput
name="asdf"
:rules="[ 'isEmail']"
/>
<!-- <FormInput label='yo <a href="#">ok</a>' type="checkbox" name="tosnlpd" :rules="[{ equals: [true], message: 'coucou' }]" /> -->
<FormInput name="safs" :rules="['isNotEmpty']" />
<FormTextarea name="saffs" :rules="['isNotEmpty']" />
<FormRadio :options="{'O': 'Oui', 'N': 'Non'}" name="yo" laebl="Yo" />
<FormRadio :options="{'O': 'Oui', 'N': 'Non'}" name="yos" label="Yos" :rules="['isNotEmpty']" :empty="true" />
/> -->
<FormInput label='yo <a href="#">ok</a>' type="checkbox" name="tosnlpd" :rules="[{ equals: [true], message: 'coucou' }]" />
<FormInput name="yo" type="number" :rules="['isNotEmpty', {min: [1]}, {max: [25]}]" />
<!-- <FormTextarea name="saffs" :rules="['isNotEmpty']" /> -->
<!-- <FormRadio :options="{'O': 'Oui', 'N': 'Non'}" name="yo" laebl="Yo" />
<FormRadio :options="{'O': 'Oui', 'N': 'Non'}" name="yos" label="Yos" :rules="['isNotEmpty']" :empty="true" /> -->
<FormSubmit>
Submit
</FormSubmit>
Expand Down
76 changes: 52 additions & 24 deletions src/runtime/composables/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,71 @@ import * as validators from 'class-validator'
import { Form } from '#imports'

export const useFormValidator = () => {
const validatorTypeMap = new Map([
['number', ['isNumber', 'min', 'max', 'isInt', 'isFloat', 'isDivisibleBy', 'isPositive', 'isNegative', 'isLessThan', 'isGreaterThan', 'isLatitude', 'isLongitude', 'isPort']],
['date', ['isDate', 'minDate', 'maxDate', 'isBefore', 'isAfter']],
['boolean', ['isBoolean']],
['array', ['arrayContains', 'arrayNotContains', 'arrayNotEmpty', 'arrayUnique']]
])

const getFieldErrors = (form: Form, field: string): string[] => {

const errors = ref<string[]>([])
const getExpectedType = (validatorName: string): string => {
for (const [type, validatorList] of validatorTypeMap) {
if (validatorList.includes(validatorName)) return type
}
return 'string'
}

if (!form.validator.rules[field]) {
return []
}
const convertValue = (value: string, expectedType: string): any => {
switch (expectedType) {
case 'number': return Number(value)
case 'boolean': return value.toLowerCase() === 'true'
case 'date': return new Date(value)
case 'array': return tryParseJSON(value) ?? value.split(',').map(item => item.trim())
default: return value
}
}

form.validator.rules[field].forEach((rule: any) => {

const validator = validators[rule.$params.type as keyof typeof validators]
const tryParseJSON = (value: string): any => {
try {
return JSON.parse(value)
} catch {
return 2
}
}

const getFieldErrors = (form: Form, field: string): string[] => {
if (!form.validator.rules[field]) return []

const type_rules = [ 'isEmail', 'isNumber' ]
// validate type only if field is not empty
if (type_rules.includes(rule.$params.type) && (form.data.state[field].length === 0 || form.data.state[field] === null)) {
return
const errors: string[] = []
const fieldValue = form.data.state[field]

for (const rule of form.validator.rules[field]) {
const validatorName = rule.$params.type as keyof typeof validators
const validator = validators[validatorName]

if (!validator) {
console.error(`Validator ${validatorName} not found`)
continue
}

if (field in form.data.state) {
// @ts-ignore TODO: import only validation functions
const result = validator(form.data.state[field].toString(), rule.$params.options)
if (['isEmail', 'isNumber'].includes(validatorName) && !fieldValue) continue

if (!result && (form.data.state[field].length > 0 && form.data.state[field] !== 'false' || ['error', 'validate'].includes(form.state.status))) {
errors.value.push(rule.custom_message ?? rule.$message)
if (fieldValue !== undefined) {
const expectedType = getExpectedType(validatorName)
const convertedValue = convertValue(fieldValue, expectedType)

if (!validator(convertedValue, ...(rule.$params.options || [])) &&
(fieldValue.length > 0 && fieldValue !== 'false' || ['error', 'validate'].includes(form.state.status))) {
errors.push(rule.custom_message ?? rule.$message)
}
}

})
}

return errors.value
return errors
}

const validateFields = async (form: Form): Promise<boolean> => {
return !Object.keys(form.validator.rules).some((field: string) => getFieldErrors(form, field).length > 0)
}
const validateFields = (form: Form): boolean =>
Object.keys(form.validator.rules).every((field: string) => getFieldErrors(form, field).length === 0)

return {
getFieldErrors,
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/messages/validators/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export const en: {[key: string]: string} = {
equals: 'This field must be equal to {0}',
contains: 'This field must contain {0}',
matches: 'This field must match the format {0}',
max: 'This field must be less than or equal to {0}',
min: 'This field must be greater than or equal to {0}',
isEmail: 'This field must be a valid email address',
isURL: 'This field must be a valid URL',
isMACAddress: 'This field must be a valid MAC address',
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/messages/validators/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export const fr: {[key: string]: string} = {
equals: 'Ce champ doit être égal à {0}',
contains: 'Ce champ doit contenir {0}',
matches: 'Ce champ doit correspondre au format {0}',
min: 'Ce champ doit être supérieur ou égal à {0}',
max: 'Ce champ doit être inférieur ou égal à {0}',
isEmail: 'Ce champ doit être une adresse email valide',
isURL: 'Ce champ doit être une URL valide',
isMACAddress: 'Ce champ doit être une adresse MAC valide',
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/utils/validators/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class FormValidator {
return { $params: { type: r }, $message: r }
}
const rule = Object.keys(r)[0]
return { $params: { type: rule, options: [...r[rule]] }, $message: r, custom_message: r.message }
return { $params: { type: rule, options: [...r[rule]] }, $message: rule, custom_message: r.message }
})

this.updateValidatorMessages(options.messages)
Expand Down

0 comments on commit b9d0b61

Please sign in to comment.