A typesafe validation & parsing library for TypeScript.
MIT License
Bot releases are visible (Hide)
Published by jviide about 2 years ago
defineProperty
shenanigans.null()
, undefined()
, ...).v.object(...)
, hopefully clarifying it a bit. For details take a look at the relevant code.Published by jviide over 2 years ago
This release modifies how object()
and record()
handle input that contains a property called __proto__.
As it happens, __proto__ is a bit special in JavaScript, as it allows the [[Prototype]] of an object to be mutated:
let obj = {}
obj.__proto__ = { a: 1 }
obj.a === 1 // true
Interestingly, JSON.parse doesn't set the prototype based on __proto__:
let input = JSON.parse('{ "__proto__": { "a": 1 } }')
input.a === 1 // false
input.__proto__.a === 1 // true
// cloning with e.g. Object.assign sets the prototype, though
Object.assign({}, input).a === 1 // true
JSON.parse is often used to parse input in HTTP servers. So, as Valita was built for validating & parsing arbitrary input data, it may encounter data that contains the __proto__
property. Some weird effects could be observed when Valita versions v0.1.2 and earlier encountered such input and needed to clone the input object:
import * as v from "@badrap/valita"
let t = v.object({ a: v.string().optional() }).rest(v.unknown().map((x) => x))
let o = t.parse(JSON.parse('{ "__proto__": { "a": 1 } }'))
// __proto__ is defined in the output...
o.__proto__.a === 1 // true
// ...but o.a shouldn't be, yet it is
o.a === 1 // true
This is now mitigated in Valita v0.1.3:
import * as v from "@badrap/valita"
let t = v.object({ a: v.string().optional() }).rest(v.unknown().map((x) => x))
let o = t.parse(JSON.parse('{ "__proto__": { "a": 1 } }'))
// __proto__ is still defined in the output...
o.__proto__.a === 1 // true
// ...and o.a isn't
o.a === undefined // true
--disable-proto=delete
option. Deno has disabled this behavior altogether by default.--disable-proto=throw
, which is a good step for catching sources of __proto__ shenanigans.Published by jviide over 2 years ago
Published by jviide over 2 years ago
v.record(v.unknown())
and v.object({ ... }).rest(v.unknown())
.v.object({ a: v.string().optional() }).parse({})
failing when it shouldn't.Missing value errors from objects are now all collected:
const t = v.object({ a: v.string(), b: v.string() });
t.parse({});
// ValitaError: missing_value at .b (missing value) (+ 1 other issue)
When in strict mode (which is the default) all unrecognized keys in an object are collected into one error:
const t = v.object({});
t.parse({ x: 1, y: 2 });
// ValitaError: unrecognized_keys at . (unrecognized keys "x" and "y")
Published by jviide almost 3 years ago
Breaking change: The default parsing mode is now "strict"
. This means that extra keys for parsed objects cause the parsing to fail by default. For example this now fails:
import * as v from "@badrap/valita";
const t = v.object({ a: v.number() });
t.parse({ "a": 1, "b": 2 });
// Uncaught ValitaError: unrecognized_key at . (unrecognized key "b")
You can go back to the default parsing mode ("passthrough"
) by giving the mode to .parse(...)
(or .try(...)
). This will not fail, even with the extra key "b"
present:
t.parse({ "a": 1, "b": 2 }, { mode: "passthrough" });
You can also opt-in to allowing extra keys case-by-case basis by using .rest(...)
:
const t = v.object({ a: v.number() }).rest(v.unknown());
t.parse({ "a": 1, "b": 2 });
// Succeeds, and the output type is { [x: string]: unknown; a: number; }
Docs galore: Huge thanks to @marvinhagemeister for documenting most of the API 🎉 (#20)