0.6.5 • Published 2 years ago

rustic-validator v0.6.5

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

Test status CodeQL status Dependenies

Rustic validator

Overview

It`s like LEGO© for data validation. Just write some simple rules, add any messages and join them together.

Example

import { validate } from "rustic-validator"

const isNonEmpty = (val: string): boolean => val.length > 0
const isName = (val: string): boolean => /^\w+$/.test(val)
const isAlice = (val: string): boolean => val === "Alice"
const onlyHumanRule = [isName, "You are not human"] as const
const onlyAliceRule = [
  isAlice,
  (val: string) => `You are ${val}, not Alice!`,
] as const

validate("Bob", isNonEmpty, onlyHumanRule, onlyAliceRule)
// => [ false, 'You are Bob, not Alice!' ]
validate("Alice", isNonEmpty, onlyHumanRule, onlyAliceRule)
// => [ true ]

Description

This library gets a test suit like the list of rules, and the rule is ordinary function (value: unknown) => boolean, singe or paired with the message. Use native JS, without wired chaining with fancy probe names.

Test suit passes from first to last rule, stoping on the first fail case. Full scan version included too. Any value type is allowed.

Moreover, with the main validation library come some helpful utils to process check results. For example - get a resume for the complex result. See below.

Full TS support and full test cover included.

Install

# using yarn
yarn add rustic-validator
# using npm
npm install rustic-validator

Important about rules notations

Due to the lack of JS native type Tuple, it is a little bit tricky to have simplicity at rules and type-safe checking at one time.

while this in place notation works well, as TS correctly get type as Tuple [(unknown) => boolean, string]

validate("foo", [checkFn, "message"])

this example will not work, because TS cast rule as Array (string | (unknown) => boolean)[], not Tuple.

const rule = [checkFn, "message"]
validate("foo", rule) // TS complain (string | (unknown) => boolean)[] IS NOT [(unknown) => boolean, string]

As a workaround its exists some ways:

  • use (string | (unknown) => boolean)[] instead [(unknown) => boolean, string], but its pointless by non-type safe at all

  • export all rules types and use them, but they are using generic notation like ShortRule<T> plus it needed to correct set to every rule

  • use tricky notation [checkFn, "message"] as const to ensure TS use Tuple type

So, here used last solutions as the simplest and brief

const rule = [checkFn, "message"] as const
validate("foo", rule) // all works well

/* type safe  */
const wrongRule = ["message", checkFn] as const
validate("foo", wrongRule) // will rase type error

Also, its may be used helper from utils (see below)

import { makeRule } from "rustic-validator/utils"

const isName = (val: string): boolean => /^\w+$/.test(val)
const onlyHumanRule = makeRule(isName, "You are not human")

// => onlyHumanRule type is Tuple [(val: string) => boolean, string]

Usage

Library export next functions:

validate()

import { validate } from "rustic-validator"

// (unknown, (unknown) => boolean) => [boolean]
validate("foo", checkFn)
// (unknown, [(unknown) => boolean]) => [boolean]
validate("foo", [checkFn])
// (unknown, [(unknown) => boolean, string]) => [boolean, string?]
validate("foo", [checkFn, "message"])
// (unknown, [(unknown) => boolean, number]) => [boolean, number?]
validate("foo", [checkFn, 42])
// (unknown, [(unknown) => boolean, (unknown) => string]) => [boolean, string?]
validate("foo", [checkFn, messageFn])
// any number and any kind of rules allowed
validate(
  "foo",
  [checkFn, messageFn], /// - execute success
  checkFn2, //           // - execute success
  [checkFn3, "message"], // - fail here
  [checkFn4, 401] //    // - not executed
)
// => [false, 'message']

validate("Bob", [(val) => val === "Alice", (val) => `${val} not Alice`])
// => [false, 'Bob not Alice']

Process test suite for value, on full success return one element list [ true ], on first fail one or two element list [isValid: boolean, message?: unknown] (depend on fail rule).

checkAll()

import { checkAll } from "rustic-validator"

// (unknown, (unknown) => boolean) => [[boolean]]
checkAll("foo", checkFn)
// (unknown, (unknown) => boolean, (unknown) => boolean) => [[boolean], [boolean]]
checkAll("foo", checkFn, checkFn2)
// (unknown, [(unknown) => boolean]) => [[boolean]]
checkAll("foo", [checkFn])
// (unknown, [(unknown) => boolean, string]) => [[boolean, string?]]
checkAll("foo", [checkFn, "message"])
// (unknown, [(unknown) => boolean, number]) => [[boolean, number?]]
checkAll("foo", [checkFn, 42])
// (unknown, [(unknown) => boolean, (unknown) => string]) => [[boolean, string?]]
checkAll("foo", [checkFn, messageFn])
// any number and any kind of rules allowed
checkAll(
  "foo",
  [checkFn, messageFn], /// - execute success
  checkFn2, //           // - execute success
  [checkFn3, "message"], // - fail here
  [checkFn4, 42] //      // - will execute
)
// => [[true], [true], [false, 'message'], [false, 42]]

checkAll("Bob", [(val) => val === "Alice", (val) => `${val} not Alice`])
// => [[false, 'Bob not Alice']]

Process full test suite for value, return full resultset list [[true], [isValid: boolean, message?: unknown]...].

getStatus()

import { getStatus } from "rustic-validator/utils"

// ([boolean]) => boolean
getStatus([false])
// ([[boolean]]) => boolean
getStatus([[false]])
// ([[boolean], [boolean]]) => boolean
getStatus([[true], [false]])
// ([[boolean, string]]) => boolean
getStatus([[false, "message"]])
// ([[boolean, number]]) => boolean
getStatus([[false, 42]])
// ([[boolean], [boolean, string]]) => boolean
getStatus([[true], [false, "message"]])
// and some bonus structure (see "idea to use")
getStatus({
  a: [[true], [true]], //          // - if execute - pass and yield to any other
  b: [false], //                   // - if execute - fail and prevent others
  c: [[true], [false, "message"]], // - if execute - fail and prevent others
})
// => false

getStatus([[true], [true], [false, "message"], [false, 42]])
// => false

Scan some variants of results for resume, return single result - on all success true, or at first fail false. In the case of using an object with check result, any fail element will case false result, object keys iteration are not determined.

getFirstError()

import { getFirstError } from "rustic-validator/utils"

// ([boolean]) => []
getFirstError([true])
// ([boolean]) => [boolean]
getFirstError([false])
// ([[boolean]]) => [boolean]
getFirstError([[false]])
// ([[boolean], [boolean]]) => [boolean]
getFirstError([[true], [false]])
// ([[boolean, string]]) => [boolean, string]
getFirstError([[false, "message"]])
// ([[boolean, number]]) => [boolean, number]
getFirstError([[false, 42]])
// ([[boolean], [boolean, string]]) => [boolean, string]
getFirstError([[true], [false, "message"]])
// and some bonus structure (just in case)
getFirstError({
  a: [[true], [true]], //            // - if execute - pass and yield to any other
  b: [[false], [false, "message2"]], // - if execute - fail at first and prevent others
  c: [[true], [false, "message"]], //// - if execute - fail and prevent others
})
// => [false] OR [false, "message"]

getFirstError([[true], [true], [false, 42], [false, "message"]])
// => [false, 42]

Scan some variants of results for first fail element, will return empty list [] if all tests succeed (no errors), or one or two-element list [false, message?: unknown] (depend on fail rule). In case of using an object with check, result any fail element (but first in list) may be returned, object keys iteration are not determined.

makeRule()

import { makeRule } from "rustic-validator/utils"

const isName = (val: string): boolean => /^\w+$/.test(val)
const onlyHumanRule = makeRule(isName, "You are not human")

// => onlyHumanRule type is [(val: string) => boolean, string]

Helper for building correct type rule, as Tuple.

QA

Is some validation rule included? Like isEmail, isPhone etc.

No, this rule is tightly bound to business values, it's simple to use your own rules.

Is only string supported?

No, any types supported, just ensure to handle it properly at test rule function.

Why does it exist?

At first - because I don't need form at modern web application with the state

Is it for webform only?

No, its works well with any data from any source.

Its for browser only?

No, use it in any environment, it's the zero-dependency library.

What about a size?

All library size less 1 kB Minified (384 B Minified + Gzipped)

Idea to use

It may be interesting to try this validation with the latest Vue with composition API and Pinia. As concept

// pinia store user.ts
import { defineStore } from 'pinia'
import { validate } from 'rustic-validator'

export const useUserStore = defineStore('model/user', () => {
  const userName = ref('')
  const userPassword = ref('')
  const userNameValidations = [/* some validation here */]
  const passwordValidations = [/* some validation here */]

  const modelValidation = computed(() => {
    userName: validate(userName.value, ...userNameValidations),
    userPassword: validate(userPassword.value, ...passwordValidations),
  })

  return {
    userName,
    userPassword,
    modelValidation,
  }
})

// vue component userLogin.ts
<template>
  <input v-model.trim="userName" :class="{'error': !modelValidation.userName[0]}">
  <p v-if="!modelValidation.userName[0]" class="error">
    {{ modelValidation.userName[1] }}
  </p>

  <input v-model.trim="userPassword" :class="{'error': !modelValidation.userPassword[0]}">
  <p v-if="!modelValidation.userPassword[0]" class="error">
    {{ modelValidation.userPassword[1] }}
  </p>

  <button :disabled="!isFormValid">Login</button>
</template>

<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { getStatus } from 'rustic-validator/utils'
import { useUserAuthStore } from '~/stores/model/user'

const userAuth = useUserAuthStore()
const { userName, userPassword, modelValidation } = storeToRefs(userAuth)
const isFormValid = computed(() => getStatus(modelValidation.value))
</script>

See also

  • v8n JavaScript fluent validation library
  • tsfv Typescript Fluent Validation Library
  • v9s Simple TypeScript-based validations

Contributing

Want to help? Contributions are welcome, but please be sure before submitting a pull request that you have first opened an issue to discuss the work with the maintainers first. This will ensure we're all on the same page before any work is done.

Send your feedback

Have some ideas, problems, or questions about the project? Post an issue in this repo.

License

The MIT License 2021 - Dmitrii Karpich.

0.6.5

2 years ago

0.6.4

2 years ago

0.6.3

2 years ago

0.6.1

2 years ago

0.5.3

2 years ago

0.5.1

2 years ago