Giter Club home page Giter Club logo

superstruct's Introduction

A simple and composable way
to validate data in JavaScript (and TypeScript).



UsageWhy?PrinciplesDemoExamplesDocumentation



Superstruct makes it easy to define interfaces and then validate JavaScript data against them. Its type annotation API was inspired by Typescript, Flow, Go, and GraphQL, giving it a familiar and easy to understand API.

But Superstruct is designed for validating data at runtime, so it throws (or returns) detailed runtime errors for you or your end users. This is especially useful in situations like accepting arbitrary input in a REST or GraphQL API. But it can even be used to validate internal data structures at runtime when needed.


Usage

Superstruct allows you to define the shape of data you want to validate:

import { assert, object, number, string, array } from 'superstruct'

const Article = object({
  id: number(),
  title: string(),
  tags: array(string()),
  author: object({
    id: number(),
  }),
})

const data = {
  id: 34,
  title: 'Hello World',
  tags: ['news', 'features'],
  author: {
    id: 1,
  },
}

assert(data, Article)
// This will throw an error when the data is invalid.
// If you'd rather not throw, you can use `is()` or `validate()`.

Superstruct ships with validators for all the common JavaScript data types, and you can define custom ones too:

import { is, define, object, string } from 'superstruct'
import isUuid from 'is-uuid'
import isEmail from 'is-email'

const Email = define('Email', isEmail)
const Uuid = define('Uuid', isUuid.v4)

const User = object({
  id: Uuid,
  email: Email,
  name: string(),
})

const data = {
  id: 'c8d63140-a1f7-45e0-bfc6-df72973fea86',
  email: '[email protected]',
  name: 'Jane',
}

if (is(data, User)) {
  // Your data is guaranteed to be valid in this block.
}

Superstruct can also handle coercion of your data before validating it, for example to mix in default values:

import { create, object, number, string, defaulted } from 'superstruct'

let i = 0

const User = object({
  id: defaulted(number(), () => i++),
  name: string(),
})

const data = {
  name: 'Jane',
}

// You can apply the defaults to your data while validating.
const user = create(data, User)
// {
//   id: 0,
//   name: 'Jane',
// }

And if you use TypeScript, Superstruct automatically ensures that your data has proper typings whenever you validate it:

import { is, object, number, string } from 'superstruct'

const User = object({
  id: number(),
  name: string()
})

const data: unknown = { ... }

if (is(data, User)) {
  // TypeScript knows the shape of `data` here, so it is safe to access
  // properties like `data.id` and `data.name`.
}

Superstruct supports more complex use cases too like defining arrays or nested objects, composing structs inside each other, returning errors instead of throwing them, and more! For more information read the full Documentation.


Why?

There are lots of existing validation libraries—joi, express-validator, validator.js, yup, ajv, is-my-json-valid... But they exhibit many issues that lead to your codebase becoming hard to maintain...

  • They don't expose detailed errors. Many validators simply return string-only errors or booleans without any details as to why, making it difficult to customize the errors to be helpful for end-users.

  • They make custom types hard. Many validators ship with built-in types like emails, URLs, UUIDs, etc. with no way to know what they check for, and complicated APIs for defining new types.

  • They don't encourage single sources of truth. Many existing APIs encourage re-defining custom data types over and over, with the source of truth being spread out across your entire code base.

  • They don't throw errors. Many don't actually throw the errors, forcing you to wrap everywhere. Although helpful in the days of callbacks, not using throw in modern JavaScript makes code much more complex.

  • They're tightly coupled to other concerns. Many validators are tightly coupled to Express or other frameworks, which results in one-off, confusing code that isn't reusable across your code base.

  • They use JSON Schema. Don't get me wrong, JSON Schema can be useful. But it's kind of like HATEOAS—it's usually way more complexity than you need and you aren't using any of its benefits. (Sorry, I said it.)

Of course, not every validation library suffers from all of these issues, but most of them exhibit at least one. If you've run into this problem before, you might like Superstruct.

Which brings me to how Superstruct solves these issues...


Principles

  1. Customizable types. Superstruct's power is in making it easy to define an entire set of custom data types that are specific to your application, and defined in a single place, so you have full control over your requirements.

  2. Unopinionated defaults. Superstruct ships with native JavaScript types, and everything else is customizable, so you never have to fight to override decisions made by "core" that differ from your application's needs.

  3. Composable interfaces. Superstruct interfaces are composable, so you can break down commonly-repeated pieces of data into components, and compose them to build up the more complex objects.

  4. Useful errors. The errors that Superstruct throws contain all the information you need to convert them into your own application-specific errors easy, which means more helpful errors for your end users!

  5. Familiar API. The Superstruct API was heavily inspired by Typescript, Flow, Go, and GraphQL. If you're familiar with any of those, then its schema definition API will feel very natural to use, so you can get started quickly.


Demo

Try out the live demo on CodeSandbox to get an idea for how the API works, or to quickly verify your use case:

Demo screenshot.


Examples

Superstruct's API is very flexible, allowing it to be used for a variety of use cases on your servers and in the browser. Here are a few examples of common patterns...


Documentation

Read the getting started guide to familiarize yourself with how Superstruct works. After that, check out the full API reference for more detailed information about structs, types and errors...

Docs screenshot.


License

This package is MIT-licensed.

superstruct's People

Contributors

aldipower avatar arturmuller avatar azarattum avatar barbogast avatar birtles avatar bobbyhadz avatar brandon93s avatar cwouam avatar davidchase avatar decentm avatar dependabot-preview[bot] avatar dependabot[bot] avatar dko-slapdash avatar elcapo avatar flaque avatar gelio avatar giubatt avatar gr2m avatar ianstormtaylor avatar ifiokjr avatar istarkov avatar jormaechea avatar lukeed avatar lynxtaa avatar mcmire avatar metalsm1th avatar nhagen avatar panva avatar quartercastle avatar thesunny avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

superstruct's Issues

Error nesting structs

When trying to nest TreeNode structs like below I came up with error:

const Node = struct({
  id: 'number',
  children: [Node]
})

JSFiddle

Maybe I'm missing something, so how can we support such kind of validation? I can such feature pretty handy in slate's schema, is slate going to has this library as dependency?

recursive schema?

Hi, I'm wondering if superstruct can support recursive schema? For example

const BinaryTree = struct({
  value: 'any',
  left: struct.optional(BinaryTree),
  right: struct.optional(BinaryTree),
});

Intuitively I feel this should be supported already, but I don't know of a way to implement, can you advise? Thanks!

Type Definitions

Hello,

Is there a plan to add typescript/flow type definitions?

Thank you!

rename the implicit `object` struct to `shape`

To get rid of the potential confusion between struct('object') and struct.object. The same way that struct('array') and struct.list are separated. Open to ideas for better names that shape.

Edit: Actually record may be a better name than shape.

Error when bundle with webpack

Unexpected token: name (StructError) [./node_modules/superstruct/lib/index.es.js:7,0][index.ef29450fbb2e4dc5d2e2.js:147554,6]

Providing Context to Type Functions

Thank you for publishing superstruct - amazing clarity.

I am defining schemas in YAML and using superstruct to validate. If I want to do bounds checking for integers, for example, I have to resort to using struct.function(). I can't use types for this scenario because only the value is passed in (edit) - and not the key of the validated data property. If the key were passed to the type validation function, I could bind to that function to look up the additional schema parameters I need.

Please consider if it would be a good idea or a bad idea to pass along additional parameters to the type validation function.

Making a struct optional

Hello, if I want to make the author field optional, what do I have to do with this basic piece of code ?

const Article = struct({
  id: 'number',
  title: 'string',
  created_at: 'date',
  published_at: 'date?',
  author: User,
})

What I would like to do is just add the question mark sign.

Thanks.

add `enums` struct

It's pretty common to need a list of enums (eg. categories) and the current syntax is:

struct([struct.enum([...])])
struct.list([struct.enum([...])])

I think it would be nice to have:

struct.enums([...])

Allow custom error messages for user defined types

It seems like now is impossible to declare custom error messages for user types

What if in this example I want to provide 2 different error messages, one for first check The provided {value} is not email and other for length check Email can't be longer than 256 smbs

const struct = superstruct({
  types: {
    email: value => isEmail(value) && value.length < 256,
  }
})

May you allow to provide a message in validate function via throw or as a result? Or I just not found a solution.

add support for tuple structs

Right now list structs are required to have a single element definition:

struct(['string'])

But there is a use case for tuples, where the element count is fixed. You wouldn't use this for single-element arrays, so we can make multi-element arrays the syntax for tuples. For example:

struct(['string', 'number'])

Would then require data that looks like:

['a', 1]
['b', 2]
...

This would make the case of validating arguments pretty simple too, for example:

const s = struct(['string', 'number', 'object'])

function myFunction(prefix, amount, options = {}) {
  s.assert(arguments)
  ...
}

Multiple errors

When the data passed in has multiple validation errors, superstruct will throw upon the first error. I think it would be very much desirable to collect all errors and return them. That would make it much more useful, specially for large complex types with many fields.

Build script fails on Windows

λ yarn run build
yarn run v1.3.2
$ yarn run build:lib && yarn run build:max && yarn run build:min
$ babel ./src --out-dir ./lib
src\default-types.js -> lib\default-types.js
src\index.js -> lib\index.js
src\struct-error.js -> lib\struct-error.js
src\superstruct.js -> lib\superstruct.js
$ mkdir -p ./dist && browserify ./src/index.js --transform babelify --standalone Superstruct > ./dist/superstruct.js
The syntax of the command is incorrect.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I'll try to work on this.

add `interface` struct

Similar to other interface implementations, and unlike the existing struct, where it doesn't validate the entire shape of an object, but just ensures that certain properties are available.

add schema builder

Hi @ianstormtaylor

I really like your library because I have used ajv and joi as well and I was frustrated from these libraries.
I would like to use your library with abstraction layer because I do not want to write types as string Because I will write typos like 'string' 'String' and others variants :)

I saw very nice abstraction on the top of ajv.
https://github.com/RomAnoX/ajv-schema-builder

What do you think about this idea?
Thank you

extend core types

First off awesome library. You have saved me from the muck of json schema!
I find myself having to call superstruct a lot to add date and email validation. I was wondering if it is possible, or could be possible, to extend the core struct instance to have additonal types defined for all structs.
I was doing a special exported struct that I consumed but having to know and manage the require path is getting a tiny bit hairy. Would love your thoughts on this?

How to perform asynchronous validation?

Hello, I want to get your thoughts on potentially supporting async validation?

My use case: validating an object by querying a service that some key actually exists in backend.

const Config = struct.async({
  name: val => val === 'a' || val === 'b',
  key: k => axios.get(`/query-service/${k}`),
  //              ^ assume this Promise resolves 'abc' or rejects 'not found'
});

try {
  const config = await Config({ name : 'a', key: 'some-existing-key' }) // {name: 'a', key: 'abc'}
} catch (err) {
  console.log(err) // 'not found'
}

add `partial` struct

To parallel, the default object struct, only verifying existing properties, and not throwing for unknown properties passed in.

I think this needs to be separate from interface, because interface doesn't perform other checks.

add `constant` struct

For when you need a value to be an exact constant.

const User = struct({
  id: 'number',
  object: struct.constant('user'),
  name: 'string',
  email: 'string',
})

consider adding value normalizing

This is the opposite of coercion, and instead acts on the value after it has been normalized. This can also be useful in cases where you need data in a canonical representation, but you want to allow a few different ones. Although, this might be solved with coercion already

How to serialize/persist a struct schema?

Hi Ian, good to see you here, this library hits a pain point!

Previously I've involved in a JS library tracking user behaviour. For each site using it, site admins can configure tracking rules, which results in a dynamic schema to persist in DB. We picked JSON schema for that case, which works but looks verbose. So here comes the point: for dynamic schemas, can we serialize the struct to an exchangeable format, them parsing them at runtime? As a side effect supporting this feature, even importing JSON schema as struct seems possible.

Hope for your thoughts on this idea, thanks.

A custom property with a default value throws an error

Me again 🙂


When using a struct as the type for a property, it throws an error even if a default value is specified.

See it in action: https://runkit.com/embed/z10qks2qv2v1

const User = superstruct.struct({
  id: 'number',
  name: 'string',
})

const Article = superstruct.struct({
  id: 'number',
  title: 'string',
  author: User,
}, {
  author: {
    id: 1000,
    name: 'Owner',
  },
})

// Passes as expected
Article({
  id: 1,
  title: 'This works as expected',
  author: {
    id: 2,
    name: 'Contributor',
  },
})

// Fails :(
Article({
  id: 1,
  title: 'This does not work!',
})

Error building minified UMD bundle

Following error is encountered when installing superstruct from ground.

Environment: macOS 10.13 / Node V8.3.0

➜  superstruct git:(master) npm i
✔ Installed 29 packages
✔ Linked 441 latest versions
[fsevents] Success: "/Users/ewind/code/playground/superstruct/node_modules/[email protected]@fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node" already installed
Pass --update-binary to reinstall or --build-from-source to recompile
✔ Run 1 scripts
yarn run v1.0.2
$ yarn run build:es && yarn run build:cjs && yarn run build:max && yarn run build:min

./src/index.js → ./lib/index.es.js...
created ./lib/index.es.js in 694ms

./src/index.js → ./lib/index.js...
created ./lib/index.js in 1.2s

./src/index.js → ./umd/superstruct.js...
created ./umd/superstruct.js in 821ms
/bin/sh: uglifyjs: command not found

./src/index.js → stdout...
created stdout in 1.1s
Error: write EPIPE
    at _errnoException (util.js:1022:11)
    at WriteWrap.afterWrite (net.js:862:14)
error Command failed with exit code 127.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
✖ Install fail! Error: Run "sh -c yarn run build" error, exit code 1
Error: Run "sh -c yarn run build" error, exit code 1
    at ChildProcess.proc.on.code (/Users/ewind/.nvm/versions/node/v8.3.0/lib/node_modules/cnpm/node_modules/runscript/index.js:74:21)
    at emitTwo (events.js:125:13)
    at ChildProcess.emit (events.js:213:7)
    at maybeClose (internal/child_process.js:927:16)
    at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5)

This error message can be tracked to the build:min script in package.json:

"build:min": "NODE_ENV=production rollup --config ./config/rollup-umd-min.js | uglifyjs > ./umd/superstruct.min.js",

This also results in error running yarn run build:

➜  superstruct git:(fix-build) yarn run build
yarn run v1.0.2
$ yarn run build:es && yarn run build:cjs && yarn run build:max && yarn run build:min

./src/index.js → ./lib/index.es.js...
created ./lib/index.es.js in 637ms

./src/index.js → ./lib/index.js...
created ./lib/index.js in 613ms

./src/index.js → ./umd/superstruct.js...
created ./umd/superstruct.js in 582ms
/bin/sh: uglifyify: command not found

./src/index.js → stdout...
created stdout in 619ms
Error: write EPIPE
    at _errnoException (util.js:1022:11)
    at WriteWrap.afterWrite (net.js:862:14)
error Command failed with exit code 127.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

The uglifyify dependency is missing bin entry, leading error for npm scripts. I've raised an issue about this, while I suggest a minify config with rollup plugin can work better with less verbose config. I'm going to PR for it 😀

Usage for other than types validations

Hey

I checked the docs, but didn't find any info if this library is supposed to be used only to validate types of data or it can be used for other validations.

For example, I can check if value is a number. Is it possible and how to check, if this value is less than 100 or more than 6?

List of unions getting mismatching error.type and error.path/value?

Hello, I have a following script to showcase something I observed. If I have a list of union in my struct, and if one the of members didn't match a union, the path / value points to the actual mismatching property that caused the union to break, but the type only stops at the union level.

I'm not sure if this is intended behavior. In my real use case I have slightly more complex data structure, and the mismatch made it a little harder for me to figure out where the issue is. Can you advise?

const {struct} = require('superstruct');

const Cat = struct({
  name: 'string',
  meow: 'string',
});

const Dog = struct({
  name: 'string',
  bark: 'string'
});

const Pet = struct.union([Cat, Dog]);

const Home = struct({
  pets: [Pet]
});

const homeData = {
  pets: [
    { name: 'pluto', bark: 'woof' },
    { name: 'hello', meow: 'kitty' },
    { name: 'vlad', flap: 'bat flapping' },
  ]
};

try {
  Home.assert(homeData);
} catch (err) {
  console.error('message =', err.message);
  console.error('type =', err.type);
  console.error('path =', err.path);
  console.error('value =', err.value);
  console.error('errors =', err.errors);
}

This prints out

message = Expected a value of type `{name,meow} | {name,bark}` for `pets.2.flap` but received `bat flapping`.

# points to the union struct
type = {name,meow} | {name,bark}    

# points to the prop that broke the union struct
path = [ 'pets', 2, 'flap' ]

# points to the prop that broke the union struct
value = bat flapping                    
errors = [ { data: { pets: [Array] },
    path: [ 'pets', 2, 'flap' ],
    value: 'bat flapping',
    errors: [Circular],
    type: '{name,meow} | {name,bark}' } ]

Question: is it possible to create a type that matches a list of structs?

Hey, I have a question. Let's say I have this payload that I want to validate:

[
  { id: '1', name: 'John', age: 43 },
  { id: '2', name: 'Miranda' }
]

Then I would create a struct for each item:

const User = struct({
  id: 'string',
  name: 'string',
  age: 'number?'
})

But since it's a list of users, how would I go about validating that? Is there a way to create a type that is a list of struct?

Supported node version

Which versions of node are supported? Currently due to dependencies only node 8+ is supported, is that intended?

error [email protected]: The engine "node" is incompatible with this module. Expected version ">=8".

cross referencing data properties

Is there any way to validate a property by cross-referencing other data values? i.e.

const schema = struct({
  foo: 'string',
  bah: (value, data) => !!(data.foo || value),
});

Defaults from function

The examples show passing a function to defaults, however when I try to do so I receive the following:

TypeError: Expected a value of type `date` for `created_at` but received `() => new Date()`.

Cannot convert object to primitive value

This line

const message = `Expected a value of type \`${type}\`${path.length ? ` for \`${path.join('.')}\`` : ''} but received \`${value}\`.`
caused an error Cannot convert object to primitive value.

Modern js frameworks like to implement objects without prototype like:

      const a = Object.create(null);
      a.x = 1;
      // The line below will fail with "Cannot convert object to primitive value"
      const www = `jhjh ${a}`;

So every time I met an error in such object instead of superstruct error I get Cannot convert object to primitive value.

I'll try to provide a fix

Validate full structure

Hi,

I need help :(
I any option to validate full structute and return all errors?

My current solution show first found error in structure

$ref another schema

I wasn't able to find in the doc a clear reference example.
Like the jsonschema feature: $ref: '#ext' // references another schema

What's the equivalent in superstruct?

consider adding value coercion

This can sometimes be useful, for example when you require numbers, but a string of a number was passed in. If there was a coercion step before validation it would account for that.

Add showcase field in README

Hi Ian, thanks for your work, Superstruct works great!

For the past days I was working with a library called Bumpover that deals data migration, whose data validation API relies on Superstruct. This library is usable for now, so how about adding its link to a "Showcase" or "Related" field in README? The more scenarios using Superstuct, the more popularized it is.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.