npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

validate-parse-type

v0.0.4

Published

This library does 3 things, in ergonomic, func-centric fashion.

Downloads

2

Readme

Validate · Parse · Type

These verbs have a kinship; performing them together enhances clarity, safety, and correctness of code.

Inspiration: https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate (In spite of this excellent post's central message, I find parse written in code doesn't make intent very clear. Instead, I'd remix from that message Parse, AND Validate — and be clear both are happening when you do it!)

validate                = cultivate robust data logic leveraging terse-yet-readable validation
validate         + type = craft runtime guarantees that outside data matches types you cast to
validate + parse        = transform data, ensuring correctness before and after transformation
validate + parse + type = alongside transformations, encode type information as it is observed

validate-parse-type exports a single function — invoked as validate<Type>({ parse: () => ... }) — which provides simple, ergonomic data correctness guarantees at the boundaries of your application. validate helps you know that the actual data you're working with matches the types you give it, without pulling in expensive schema-validation tooling to do so.

Function-centricity keeps validate lightweight (<2kb gzipped) but also powerful, and eminently flexible.

Installation

npm install --save validate-parse-type

Then, in your Javascript or Typescript files:

import validate from 'validate-parse-type';

// ~ alternately ~

import { validate } from 'validate-parse-type';

// ~ alternately ~

import { validate as nameOfYourChoice } from 'validate-parse-type';

All "atomic composition" functions (eg. validate.unless(validate.isMissing)) are included under validate as a namespace, but you may import them as separate functions if you wish:

import { validate, unless, isMissing, /* etc. */ } from 'validate-parse-type';

Validate

Pass data along with a mapping of validation messages to validator expressions:

validate(data, { "data isn't correct": !isDataCorrect(data) });
// OR
validate(data, { "data isn't correct": () => !isDataCorrect(data) });
// OR
const validatedData = validate(data, {
  "data isn't an object": typeof data !== "object",
});
// OR
const validatedData = validate(data, {
  "data doesn't exist": !data,
  "data isn't an object": () => typeof data !== "object",
  "data isn't correct": !isDataCorrect(data),
});

If any validator express is true or returns true, validation fails and an error is raised containing the text of the corresponding validator mapping key, along with a serialized form of the validated data which is useful for debugging.

Validator expressions must be either:

  • A boolean value.
  • A function that returns a boolean value. This means you must sometimes cast to boolean using !! or Boolean(...).

Function validator expressions are useful for multiple reasons:

  • They're evaluated one-at-a-time, in order of declaration, and short-circuit (meaning that only validators up to the first failing expression will execute).
  • Logic within a subsequent validator function can depend on the success of a preceeding validator function, due to the properties described above.
  • They can be executed after parsing, thus operating on parsed/transformed data.

Parse

Often when validating data, we want to also transform that data in some way related to the validation taking place. It's advantageous to do this within a single "atomic" function call instead of multiple steps, to avoid possible logic bugs. You can add a parse step to any validate call by passing a parse function:

const validatedParsedData = validate(data, {
  "data doesn't exist": !data,
  "data isn't an object": (originalData) => typeof originalData !== "object",
  parse: (data) => {
    return { abc: data.abc, xyz: data.x + data.y + data.z };
  },
  "only abc and xyz keys are allowed": (parsedData) => {
    return !parsedData.abc || !parsedData.xyz || Object.keys(parsedData) > 2;
  },
});

Any validator expressions that appear before the parse function will be passed the original, pre-parse data for validation. Those that appear after the parse function will be passed the resulting parsed data for validation.

Type

Almost always, when validating, our code "learns" new information about the data being processed. If we don't do anything with this new information then we're effectively throwing it away — apart from preventing bad-data bugs, our validation can't have an effect on code running further along in the currrent codepath.

What can we do with this new information? We can channel it into the type system. By casting to a more-specific type during the same, singular, validate/parse operation, we convey new information about the data to future code — and type- errors will tell us if the data does not meet future expectations.

Typing while you validate is very straightforward:

type PositiveInteger<T extends number> = ${T} extends -${string} | ${string}.${string} ? never : T;
// See https://stackoverflow.com/a/69413070 for details of PostitiveInteger.

// Pass the desired result type as a type argument when calling validate:
const positiveIntegerArray = validate<PositiveInteger[]>(data, {
  "data isn't an array": !Array.isArray(data),
  parse: (data) => data.map(parseInt),
  "data contains a non-integer": (parsedData) => {
    return !parsedData.every((item) => Number.isInteger(item));
  },
  "data contains a negative integer": (parsedData) => {
    return !parsedData.every((item) => item > 0);
  },
});

// From this point onward in the codepath, the type system knows that
// positiveIntegerArray is an array of positive integers, and will help us
// accordingly. This knowledge is backed by actual runtime validation.

Further Examples

  1. Basic validation:
const data = response.data;
validate(data, {
  "Data is missing": typeof data === undefined,
  "Data is not a string": typeof data !== "string",
  "Data is empty": !data?.length,
});
// Can raise
// Error: Data is missing
// Error: Data is not a string
// Error: Data is empty
  1. Validation with type-casting:
const data = response.data;
const stringData = validate<string>(data, {
  "Data is missing": typeof data === undefined,
  "Data is not a string": typeof data !== "string",
  "Data is empty": !data?.length,
});
// Can raise
// Error: Data is missing
// Error: Data is not a string
// Error: Data is empty
  1. Validate and parse:
const data = response.data;
const stringValue = validate<string>(data, {
  parse: () => data.items[0].value,
  "Value is missing": (value) => typeof value !== undefined,
  "Value is not a string": (value) => typeof value !== "string",
  "Value is empty": (value) => !value.length,
});
// Can raise
// Error: Value is missing
// Error: Value is not a string
// Error: Value is empty
  1. Validate data prior to parsing, and after parsing:
const data = response.data;
const stringValue = validate<string>(data, {
  "Data is not an object": (data) => typeof data !== "object",
  parse: () => data.items[0].value,
  "Value is not a string": (value) => typeof value !== "string",
});
// Can raise
// Error: Data is not an object
// Error: Value is not a string
  1. Use atomic composition to build conditions with consistent behavior and error messaging:
const data = response.data;
const stringValue = validate<string>(data, {
  ...validate.unless("Data", validate.isMissing, validate.nonObject, validate.isEmpty),
  parse: () => data.items[0].value,
  ...validate.unless("Value", validate.isMissing, validate.nonString, validate.isEmpty),
});
// Can raise
// Error: Data is missing
// Error: Data is not an object
// Error: Data is empty
// Error: Value is missing
// Error: Value is not an object
// Error: Value is empty

// ~ alternately ~

const data = response.data;
const stringValue = validate<string>(data, {
  "Data is invalid": [validate.isMissing, validate.nonObject, validate.isEmpty],
  parse: () => data.items[0].value,
  "Value is invalid: [validate.isMissing, validate.nonString, validate.isEmpty],
});
// Can raise
// Error: Data is invalid
// Error: Value is invalid
// (error messages are not as granular in this form)

License

MIT