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

Add support for ULID validation #58

Merged
merged 2 commits into from
Jun 27, 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
1 change: 1 addition & 0 deletions src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const messages = {
'notIn': 'The selected {{ field }} is invalid',
'ipAddress': 'The {{ field }} field must be a valid IP address',
'uuid': 'The {{ field }} field must be a valid UUID',
'ulid': 'The {{ field }} field must be a valid ULID',
'hexCode': 'The {{ field }} field must be a valid hex color code',

'boolean': 'The value must be a boolean',
Expand Down
9 changes: 9 additions & 0 deletions src/schema/string/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
urlRule,
jwtRule,
uuidRule,
ulidRule,
trimRule,
ibanRule,
alphaRule,
Expand Down Expand Up @@ -66,6 +67,7 @@ export class VineString extends BaseLiteralType<string, string, string> {
url: urlRule,
iban: ibanRule,
uuid: uuidRule,
ulid: ulidRule,
trim: trimRule,
email: emailRule,
alpha: alphaRule,
Expand Down Expand Up @@ -327,6 +329,13 @@ export class VineString extends BaseLiteralType<string, string, string> {
return this.use(uuidRule(...args))
}

/**
* Validates the value to be a valid ULID
*/
ulid() {
return this.use(ulidRule())
}

/**
* Validates the value contains ASCII characters only
*/
Expand Down
13 changes: 13 additions & 0 deletions src/schema/string/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,19 @@ export const uuidRule = createRule<{ version?: (1 | 2 | 3 | 4 | 5)[] } | undefin
}
)

/**
* Validates the value to be a valid ULID
*/
export const ulidRule = createRule((value, _, field) => {
if (!field.isValid) {
return
}

if (!helpers.isULID(value as string)) {
field.report(messages.ulid, 'ulid', field)
}
})

/**
* Validates the value contains ASCII characters only
*/
Expand Down
19 changes: 19 additions & 0 deletions src/vine/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import type { FieldContext } from '../types.js'
const BOOLEAN_POSITIVES = ['1', 1, 'true', true, 'on']
const BOOLEAN_NEGATIVES = ['0', 0, 'false', false]

const ULID = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/

/**
* Collection of helpers used across the codebase to coerce
* and type-check values from HTML forms.
Expand Down Expand Up @@ -228,6 +230,23 @@ export const helpers = {
'US',
] as const,

/**
* Check if the value is a valid ULID
*/
isULID(value: unknown): boolean {
if (typeof value !== 'string') {
return false
}

// Largest valid ULID is '7ZZZZZZZZZZZZZZZZZZZZZZZZZ'
// https://github.com/ulid/spec#overflow-errors-when-parsing-base32-strings
if (value[0] > '7') {
return false
}

return ULID.test(value)
},

/**
* Check if the value is a valid color hexcode
*/
Expand Down
54 changes: 54 additions & 0 deletions tests/integration/schema/string.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* @vinejs/vine
*
* (c) VineJS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { test } from '@japa/runner'
import vine from '../../../index.js'

test.group('String', () => {
test('fail when value is not a string', async ({ assert }) => {
const schema = vine.object({
name: vine.string(),
})

const data = { name: 42 }
await assert.validationErrors(vine.validate({ schema, data }), [
{
field: 'name',
message: 'The name field must be a string',
rule: 'string',
},
])
})

test('fail when value is not a valid ULID', async ({ assert }) => {
const schema = vine.object({
id: vine.string().ulid(),
})

const data = { id: '01J0TMIXKWW62H0BKGQ984AS' }
await assert.validationErrors(vine.validate({ schema, data }), [
{
field: 'id',
message: 'The id field must be a valid ULID',
rule: 'ulid',
},
])
})

test('pass when value is a valid ULID', async ({ assert }) => {
const schema = vine.object({
id: vine.string().ulid(),
})

const data = { id: '01J0TMSK8WMJSTX1T2633GFA4G' }
await assert.validationOutput(vine.validate({ schema, data }), {
id: '01J0TMSK8WMJSTX1T2633GFA4G',
})
})
})
6 changes: 6 additions & 0 deletions tests/unit/helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,10 @@ test.group('Vine Helpers', () => {
assert.isTrue(vine.helpers.isPostalCode('69200', 'FR'))
assert.isTrue(vine.helpers.isMobilePhone('0612345678', 'fr-FR'))
})

test('check if the value is a ULID', ({ assert }) => {
assert.isTrue(vine.helpers.isULID('01J0TSV6ZP6VTAHWZ7A7SYDBM2'))
assert.isTrue(vine.helpers.isULID('01j0tsv6zp6vtahwz7a7sydbm2'))
assert.isFalse(vine.helpers.isULID('01J0TSV6ZP6VTAHWZIL7A7SYDB'))
})
})
41 changes: 41 additions & 0 deletions tests/unit/rules/string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
passportRule,
postalCodeRule,
uuidRule,
ulidRule,
asciiRule,
ibanRule,
jwtRule,
Expand Down Expand Up @@ -1288,6 +1289,46 @@ test.group('String | uuid', () => {
.run(stringRuleValidator)
})

test.group('String | ulid', () => {
test('validate {value}')
.with([
{
errorsCount: 1,
rule: ulidRule(),
value: 22,
error: 'The dummy field must be a string',
},
{
errorsCount: 1,
rule: ulidRule(),
value: 22,
bail: false,
error: 'The dummy field must be a string',
},
{
errorsCount: 1,
rule: ulidRule(),
value: '1999010301',
error: 'The dummy field must be a valid ULID',
},
{
rule: ulidRule(),
value: '01HZW62CR5FNVW4PSXVXC1HTZF',
},
{
rule: ulidRule(),
value: '7ZZZZZZZZZZZZZZZZZZZZZZZZZ',
},
{
errorsCount: 1,
rule: ulidRule(),
value: '80000000000000000000000000',
error: 'The dummy field must be a valid ULID',
},
])
.run(stringRuleValidator)
})

test.group('String | ascii', () => {
test('validate {value}')
.with([
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/schema/string.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
passportRule,
postalCodeRule,
uuidRule,
ulidRule,
asciiRule,
ibanRule,
jwtRule,
Expand Down Expand Up @@ -667,6 +668,11 @@ test.group('VineString | applying rules', () => {
schema: vine.string().uuid(),
rule: uuidRule(),
},
{
name: 'ulid',
schema: vine.string().ulid(),
rule: ulidRule(),
},
{
name: 'ascii',
schema: vine.string().ascii(),
Expand Down
Loading