Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Quick Example | Common Use Cases | Install and Usage | Shape Rules | API
NOTE: WORK IN PROGRESS
This is a schema validator in the tradition of Joi or any JSON-Schema validator, with the key features:
Why write yet another validator? I've used Joi
for a long time, but
always found its schema definition a little verbose at the syntax
level. I've never liked JSON-Schema - it's just too noisy to
eyeball. What I do like is Vue.js property
validation,
but that only works at the top level. I did write a prototype deep
Vue property validator using
Joi, but it's pretty clunky.
This validator is motivated by two use cases: adding message validation to the Seneca microservices framework, and providing deep defaults for complex custom Vue.js components. I think it does both jobs rather nicely with an easy-to-read syntax.
This open source module is sponsored and supported by Voxgig. |
---|
const { Gubu } = require('gubu')
// Property a is optional, must be a Number, and defaults to 1.
// Property b is required, and must be a String.
const shape = Gubu({ a: 1, b: String })
// Object shape is good! Prints `{ a: 99, b: 'foo' }`
console.log( shape({ a: 99, b: 'foo' }) )
// Object shape is also good. Prints `{ a: 1, b: 'foo' }`
console.log( shape({ b: 'foo' }) )
// Object shape is bad. Throws an exception with message:
// Validation failed for path "a" with value "BAD" because the value is not of type number.
// Validation failed for path "b" with value "" because the value is required.'
console.log( shape({ a: 'BAD' }) )
As shown above, you use the exported Gubu
function to create a
validation checker (does the argument match the schema shape?). If
valid, the checker returns its first argument, otherwise it throws an
exception listing all (not just the first!) the validity errors.
Let's say you have a server that needs to run on a given host and port, but by default should run on localhost on port 8080. The host should be a non-empty string, and the port should be a number.
const optionShape = Gubu({
host: 'localhost',
port: 8080
})
// These print: { host: 'localhost', port: 8080 }
console.log(optionShape())
console.log(optionShape({}))
// Prints: { host: 'localhost', port: 9090 }
console.log(optionShape({ port: 9090 }))
// All of these throw an error.
console.log(optionShape({ host: 9090 })) // Not a string.
console.log(optionShape({ port: '9090' })) // Not a number.
console.log(optionShape({ host: '' })) // Not really a usable string!
You're building a front end component that displays data from the back end, and you want to handle bad data gracefully.
const productListShape = Gubu({
view: {
discounts: [
// All elements must match this shape.
{
name: String,
// A custom validation!
percent: (v: any) => 0 < v && v < 100
}
],
products: [
{
name: String,
price: Number,
}
]
}
})
// Oh noes! The back end gave me nothing! Luckily I can still work with a
// valid, if empty, data structure.
productListShape({}) // returns:
{ view: { discounts: [], products: [] } }
$ npm install gubu
The Gubu module has no dependencies. A single function named Gubu
is exported. Utility functions are provided as properties of Gubu
or can be exported separately.
Gubu is written in TypeScript, and can be imported naturally:
import { Gubu } from 'gubu'
Types are provided in gubu.d.ts.
A minified version is provided as gubu.min.js, which
can be directly loaded into a web page and exports a Gubu
global
object.
However you're probably better off importing this module in the usual manner for your build process and bundling it together with everything else.
The general principle of Gubu's design is that the schema shape should match a valid object or value as closely as possible.
For scalar values you can provide a native type object to make the value required:
Gubu(String)
matches strings: 'foo'
Gubu(Number)
matches numbers: 123
Gubu(Boolean)
matches booleans: true
Or defaults to make the value optional:
Gubu('bar')
matches strings: 'foo'
, and undefined
Gubu(0)
matches numbers: 123
, and undefined
Gubu(false)
matches booleans: true
, and undefined
If a value is optional and undefined
, the default value is returned:
Gubu('bar')()
returns 'bar'
.
The values null
and NaN
must match exactly. The value undefined
is special - it literally means no value.
Empty strings are not considered to be valid if a string is required
(this is usually what you want). To allow empty string, use
Gubu(Empty(String))
(where Empty
is exported by the Gubu
module).
For objects, write them as you want them:
let shape = Gubu({
foo: {
bar: {
zed: String,
qaz: Number,
}
}
})
The above shape will match:
{
foo: {
bar: {
zed: 'x',
qaz: 1
}
}
}
For arrays, the first elements is treated as the shape that all elements in the array must match:
Gubu([String])
matches ['a', 'b', 'c']
Gubu([{x:1}])
matches [{x: 11}, {x: 22}, {x: 33}]
If you need specific elements to match specific shapes, add these shapes after the first element:
Gubu([String,Number])
matches [1, 'b', 'c']
- the first element is a Number
, the rest Strings
.Thus, the element 0
of a shape array defines the general element,
and following elements define special cases (offset by 1).
You can specify custom validation using functions:
Gubu({a: (v) => 10<v })
: matches {a: 11}
as 10 < 11
And you can manipulate the value if you need to:
Gubu({a: (v,u) => 10<v ? (u.val=2*v, true) : false })
: matches {a: 11}
as 10 < 11
and returns {a: 22}
.You can also compose validations together:
const shape = Gubu({ a: Gubu({ x: Number }) })
// Matches { a: { x: 1 } } as expected
shape({ a: { x: 1 } })
Gubu exports shape "builder" utility functions that let you further
refine the shape (You've already seen the Empty
builder above that
allows strings to be empty). You wrap your value with the builder
function to apply the desired effect.
The Required
builder makes a value required:
const { Gubu, Required } = require(`gubu`)
const shape = Gubu({
a: Required({x: 1}) // Property `a` is required and must match `{x: 1}`.
})
The Closed
builder prohibits an object from having additional unspecified properties:
const { Gubu, Closed } = require(`gubu`)
// Only properties `a` and `b` are allowed.
const shape = Gubu(Closed({
a: 1,
b: true
}))
You can also access builders as properties of the main Gubu
function, and you can also chain most builders. Thus a Required
and
Closed
object can be specified with:
const { Gubu } = require(`gubu`)
const shape = Gubu({
a: Gubu.Closed({ x: 1 }).Required(),
b: Gubu.Required({ x: 1 }).Closed(), // Also works.
})
You can also write your own builders - see the API Builders section.
In addition to this README, the unit tests are comprehensive and provide many usage examples.
A shape specification can either be at the root of a JSON structure, an array element, or the value of an object property. For a value to pass validation, it is compared to the shape, and must match the constraints of the shape. If the shape has elements or properties, these must also match, and are validated recursively in a depth-first manner 1.
The value must be of the indicated type, and must exist.
String
: match any string, but not the empty string 2.Number
: match any number, but not BigInt
values.Boolean
: match any boolean.Symbol
: match any symbol.BigInt
: match any BigInt
(including the 1n
syntax form).Date
: match an object created with new Date(...)
RegExp
: match an object created with /.../
or new RegExp(...)
The value must be of the indicated type, and is derived from the given default. If the value does not exist, the default value is inserted.
foo
: match any string, but replace an empty string 3.123
: match any number, but not BigInt
values.true
: match any boolean.new Symbol('bar')
: match any symbol.new BigInt(456)
: match any BigInt
(including the 1n
syntax form).new Date()
: match an object created with new Date(...)
/x/
: match an object created with /.../
or new RegExp(...)
Unfortunately the empty string is not really a subtype of the string
type, since it evaluates to false
. In the case of HTTP input,
missing parameters values are often provided as empty strings, when
they are in fact undefined
. There are heartfelt arguments on both
sides of this issue.
The engineering compromise is based on the priniciple of explicit
notice. Since reasonable people have a reasonable disagreement about
this behaviour, a mitigation of the issue is to make it
explicit. Thus, the Empty(String)
, or Empty('foo')
shapes need to
be used if you want to accept empty strngs.
As a shortcut, you can use ''
directly for optional strings, and
that shape will accept empty strings, and give you an empty string as
a default.
This module is inspired by Joi, which I used for many years. It also draws from the way Vue does property validation.
The name comes from a sort of in-joke in Irish politics. It is grotesque, unbelievable, bizarre and unprecedented, that anyone would write yet another validation library for JavaScript, let alone a third one! (See parambulator and norma - but don't use those, Gubu is better!).
Also I like short names.
Copyright (c) 2021, Richard Rodger and other contributors. Licensed under MIT.
The implementation algorithm is iterative, just a loop that processes values in depth-first order. ↩
An empty string is not considered to match the string
type. To
allow empty strings, use Empty(String)
. See Empty
Strings. ↩
An empty string is not considered to match the string
type. To
allow empty strings, use Empty('some-default')
. See Empty
Strings. ↩
FAQs
An object shape validation utility.
The npm package gubu receives a total of 10,051 weekly downloads. As such, gubu popularity was classified as popular.
We found that gubu demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.