Comparing version 1.7.1 to 1.8.0
@@ -17,2 +17,30 @@ # Changelog | ||
# 1.8.0 | ||
- **New Feature** | ||
- add `brand` combinator (@gcanti) | ||
- add `Int` codec (@gcanti) | ||
- `exact` strips additional properties while decoding / encoding (@gcanti) | ||
- un-deprecate `strict` combinator, is now an alias of `exact(type(...))` (@gcanti) | ||
- **Bug Fix** | ||
- fix wrong context keys for tagged unions (@gcanti) | ||
- **Deprecation** | ||
- deprecate `refinement` combinator in favour of `brand` (@gcanti) | ||
- deprecate `Integer` codec in favour of `Int` (@gcanti) | ||
- deprecate `StrictType` class (@gcanti) | ||
- deprecate `StrictC` interface (@gcanti) | ||
- **Polish** | ||
- modify the implementation of `intersection` in order to support combinators that strip additional properties (@gcanti) | ||
- do not validate the codomain of a key of a record if its domain in invalid (@gcanti) | ||
- normalize missing `message` field in `ValidationError` (@gcanti) | ||
- fix name of recursive codec definitions (@gcanti) | ||
- remove unexpected validation path from partial type, closes #195 (@gcanti) | ||
- do not leak taggedUnion implementation when tag validation fails (@gcanti) | ||
- add `actual` value to all context entries (@gcanti) | ||
- `exact` now bails out when the value is not an `UnknownRecord` (@gcanti) | ||
- `tuple` should not leak the implementation (`never` usage) (@gcanti) | ||
- `exact` should not leak the implementation (`never` usage) (@gcanti) | ||
- use `Number.isInteger` in `Integer` implementation (@gcanti) | ||
- use the Flow convention to name `exact` codecs (@gcanti) | ||
# 1.7.1 | ||
@@ -19,0 +47,0 @@ |
@@ -148,3 +148,3 @@ import { Either } from 'fp-ts/lib/Either'; | ||
*/ | ||
export declare const appendContext: (c: Context, key: string, decoder: Decoder<any, any>) => Context; | ||
export declare const appendContext: (c: Context, key: string, decoder: Decoder<any, any>, actual?: unknown) => Context; | ||
/** | ||
@@ -391,3 +391,5 @@ * @since 1.0.0 | ||
/** | ||
* Use `BrandC` instead | ||
* @since 1.5.3 | ||
* @deprecated | ||
*/ | ||
@@ -397,9 +399,33 @@ export interface RefinementC<C extends Any> extends RefinementType<C, TypeOf<C>, OutputOf<C>, InputOf<C>> { | ||
/** | ||
* Use `brand` instead | ||
* @since 1.0.0 | ||
* @deprecated | ||
*/ | ||
export declare const refinement: <C extends Any>(codec: C, predicate: Predicate<C["_A"]>, name?: string) => RefinementC<C>; | ||
/** | ||
* Use `Int` instead | ||
* @since 1.0.0 | ||
* @deprecated | ||
*/ | ||
export declare const Integer: RefinementC<NumberC>; | ||
declare const _brand: unique symbol; | ||
/** | ||
* @since 1.8.0 | ||
*/ | ||
export interface Brand<B> { | ||
readonly [_brand]: B; | ||
} | ||
/** | ||
* @since 1.8.0 | ||
*/ | ||
export interface BrandC<C extends Any, B> extends RefinementType<C, TypeOf<C> & Brand<B>, OutputOf<C>, InputOf<C>> { | ||
} | ||
/** | ||
* @since 1.8.0 | ||
*/ | ||
export declare const brand: <C extends Any, B extends string>(codec: C, predicate: Predicate<C["_A"]>, name: B) => BrandC<C, B>; | ||
/** | ||
* @since 1.8.0 | ||
*/ | ||
export declare const Int: BrandC<NumberC, "Int">; | ||
declare type LiteralValue = string | number | boolean; | ||
@@ -703,2 +729,3 @@ /** | ||
* @since 1.0.0 | ||
* @deprecated | ||
*/ | ||
@@ -712,2 +739,3 @@ export declare class StrictType<P, A = any, O = A, I = unknown> extends Type<A, O, I> { | ||
* @since 1.5.3 | ||
* @deprecated | ||
*/ | ||
@@ -721,7 +749,6 @@ export interface StrictC<P extends Props> extends StrictType<P, { | ||
/** | ||
* Specifies that only the given properties are allowed | ||
* @deprecated use `exact` instead | ||
* Strips additional properties | ||
* @since 1.0.0 | ||
*/ | ||
export declare const strict: <P extends Props>(props: P, name?: string) => StrictC<P>; | ||
export declare const strict: <P extends Props>(props: P, name?: string | undefined) => ExactC<TypeC<P>>; | ||
/** | ||
@@ -821,5 +848,6 @@ * @since 1.3.0 | ||
/** | ||
* Strips additional properties | ||
* @since 1.1.0 | ||
*/ | ||
export declare function exact<C extends HasProps>(codec: C, name?: string): ExactC<C>; | ||
export declare const exact: <C extends HasProps>(codec: C, name?: string) => ExactC<C>; | ||
/** | ||
@@ -826,0 +854,0 @@ * Drops the codec "kind" |
488
lib/index.js
@@ -54,5 +54,3 @@ "use strict"; | ||
} | ||
else { | ||
return ab.validate(validation.value, c); | ||
} | ||
return ab.validate(validation.value, c); | ||
}, this.encode === exports.identity && ab.encode === exports.identity ? exports.identity : function (b) { return _this.encode(ab.encode(b)); }); | ||
@@ -105,3 +103,3 @@ }; | ||
*/ | ||
exports.appendContext = function (c, key, decoder) { | ||
exports.appendContext = function (c, key, decoder, actual) { | ||
var len = c.length; | ||
@@ -112,3 +110,3 @@ var r = Array(len + 1); | ||
} | ||
r[len] = { key: key, type: decoder }; | ||
r[len] = { key: key, type: decoder, actual: actual }; | ||
return r; | ||
@@ -141,2 +139,3 @@ }; | ||
var isInterfaceCodec = getIsCodec('InterfaceType'); | ||
var isPartialCodec = getIsCodec('PartialType'); | ||
var isStrictCodec = getIsCodec('StrictType'); | ||
@@ -407,3 +406,5 @@ var isIntersectionCodec = getIsCodec('IntersectionType'); | ||
/** | ||
* Use `brand` instead | ||
* @since 1.0.0 | ||
* @deprecated | ||
*/ | ||
@@ -417,13 +418,23 @@ exports.refinement = function (codec, predicate, name) { | ||
} | ||
else { | ||
var a = validation.value; | ||
return predicate(a) ? exports.success(a) : exports.failure(a, c); | ||
} | ||
var a = validation.value; | ||
return predicate(a) ? exports.success(a) : exports.failure(a, c); | ||
}, codec.encode, codec, predicate); | ||
}; | ||
/** | ||
* Use `Int` instead | ||
* @since 1.0.0 | ||
* @deprecated | ||
*/ | ||
exports.Integer = exports.refinement(exports.number, function (n) { return n % 1 === 0; }, 'Integer'); | ||
exports.Integer = exports.refinement(exports.number, Number.isInteger, 'Integer'); | ||
/** | ||
* @since 1.8.0 | ||
*/ | ||
exports.brand = function (codec, predicate, name) { | ||
return exports.refinement(codec, predicate, name); | ||
}; | ||
/** | ||
* @since 1.8.0 | ||
*/ | ||
exports.Int = exports.brand(exports.number, Number.isInteger, 'Int'); | ||
/** | ||
* @since 1.0.0 | ||
@@ -504,2 +515,3 @@ */ | ||
cache = definition(Self); | ||
cache.name = name; | ||
} | ||
@@ -540,29 +552,27 @@ return cache; | ||
return new ArrayType(name, function (u) { return exports.UnknownArray.is(u) && u.every(codec.is); }, function (u, c) { | ||
var arrayValidation = exports.UnknownArray.validate(u, c); | ||
if (arrayValidation.isLeft()) { | ||
return arrayValidation; | ||
var unknownArrayValidation = exports.UnknownArray.validate(u, c); | ||
if (unknownArrayValidation.isLeft()) { | ||
return unknownArrayValidation; | ||
} | ||
else { | ||
var xs = arrayValidation.value; | ||
var len = xs.length; | ||
var a = xs; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var x = xs[i]; | ||
var validation = codec.validate(x, exports.appendContext(c, String(i), codec)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
var vx = validation.value; | ||
if (vx !== x) { | ||
if (a === xs) { | ||
a = xs.slice(); | ||
} | ||
a[i] = vx; | ||
var us = unknownArrayValidation.value; | ||
var len = us.length; | ||
var as = us; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var ui = us[i]; | ||
var validation = codec.validate(ui, exports.appendContext(c, String(i), codec, ui)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
var ai = validation.value; | ||
if (ai !== ui) { | ||
if (as === us) { | ||
as = us.slice(); | ||
} | ||
as[i] = ai; | ||
} | ||
} | ||
return errors.length ? exports.failures(errors) : exports.success(a); | ||
} | ||
return errors.length > 0 ? exports.failures(errors) : exports.success(as); | ||
}, codec.encode === exports.identity ? exports.identity : function (a) { return a.map(codec.encode); }, codec); | ||
@@ -585,5 +595,5 @@ }; | ||
var getNameFromProps = function (props) { | ||
return "{ " + Object.keys(props) | ||
return Object.keys(props) | ||
.map(function (k) { return k + ": " + props[k].name; }) | ||
.join(', ') + " }"; | ||
.join(', '); | ||
}; | ||
@@ -598,2 +608,5 @@ var useIdentity = function (codecs, len) { | ||
}; | ||
var getInterfaceTypeName = function (props) { | ||
return "{ " + getNameFromProps(props) + " }"; | ||
}; | ||
/** | ||
@@ -604,3 +617,3 @@ * @alias `interface` | ||
exports.type = function (props, name) { | ||
if (name === void 0) { name = getNameFromProps(props); } | ||
if (name === void 0) { name = getInterfaceTypeName(props); } | ||
var keys = Object.keys(props); | ||
@@ -621,37 +634,35 @@ var types = keys.map(function (key) { return props[key]; }); | ||
}, function (u, c) { | ||
var dictionaryValidation = exports.UnknownRecord.validate(u, c); | ||
if (dictionaryValidation.isLeft()) { | ||
return dictionaryValidation; | ||
var unknownRecordValidation = exports.UnknownRecord.validate(u, c); | ||
if (unknownRecordValidation.isLeft()) { | ||
return unknownRecordValidation; | ||
} | ||
else { | ||
var o = dictionaryValidation.value; | ||
var a = o; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var k = keys[i]; | ||
if (!hasOwnProperty.call(a, k)) { | ||
var o = unknownRecordValidation.value; | ||
var a = o; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var k = keys[i]; | ||
if (!hasOwnProperty.call(a, k)) { | ||
if (a === o) { | ||
a = __assign({}, o); | ||
} | ||
a[k] = a[k]; | ||
} | ||
var ak = a[k]; | ||
var type_1 = types[i]; | ||
var validation = type_1.validate(ak, exports.appendContext(c, k, type_1, ak)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
var vak = validation.value; | ||
if (vak !== ak) { | ||
/* istanbul ignore next */ | ||
if (a === o) { | ||
a = __assign({}, o); | ||
} | ||
a[k] = a[k]; | ||
a[k] = vak; | ||
} | ||
var ak = a[k]; | ||
var type_1 = types[i]; | ||
var validation = type_1.validate(ak, exports.appendContext(c, k, type_1)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
var vak = validation.value; | ||
if (vak !== ak) { | ||
/* istanbul ignore next */ | ||
if (a === o) { | ||
a = __assign({}, o); | ||
} | ||
a[k] = vak; | ||
} | ||
} | ||
} | ||
return errors.length ? exports.failures(errors) : exports.success(a); | ||
} | ||
return errors.length > 0 ? exports.failures(errors) : exports.success(a); | ||
}, useIdentity(types, len) | ||
@@ -686,2 +697,5 @@ ? exports.identity | ||
exports.PartialType = PartialType; | ||
var getPartialTypeName = function (inner) { | ||
return "Partial<" + inner + ">"; | ||
}; | ||
/** | ||
@@ -691,10 +705,6 @@ * @since 1.0.0 | ||
exports.partial = function (props, name) { | ||
if (name === void 0) { name = "Partial<" + getNameFromProps(props) + ">"; } | ||
if (name === void 0) { name = getPartialTypeName(getInterfaceTypeName(props)); } | ||
var keys = Object.keys(props); | ||
var types = keys.map(function (key) { return props[key]; }); | ||
var len = keys.length; | ||
var partials = {}; | ||
for (var i = 0; i < len; i++) { | ||
partials[keys[i]] = exports.union([types[i], undefinedType]); | ||
} | ||
return new PartialType(name, function (u) { | ||
@@ -706,3 +716,4 @@ if (!exports.UnknownRecord.is(u)) { | ||
var k = keys[i]; | ||
if (!partials[k].is(u[k])) { | ||
var uk = u[k]; | ||
if (uk !== undefined && !props[k].is(uk)) { | ||
return false; | ||
@@ -713,31 +724,29 @@ } | ||
}, function (u, c) { | ||
var dictionaryValidation = exports.UnknownRecord.validate(u, c); | ||
if (dictionaryValidation.isLeft()) { | ||
return dictionaryValidation; | ||
var unknownRecordValidation = exports.UnknownRecord.validate(u, c); | ||
if (unknownRecordValidation.isLeft()) { | ||
return unknownRecordValidation; | ||
} | ||
else { | ||
var o = dictionaryValidation.value; | ||
var a = o; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var k = keys[i]; | ||
var ak = a[k]; | ||
var type_2 = partials[k]; | ||
var validation = type_2.validate(ak, exports.appendContext(c, k, type_2)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
var vak = validation.value; | ||
if (vak !== ak) { | ||
/* istanbul ignore next */ | ||
if (a === o) { | ||
a = __assign({}, o); | ||
} | ||
a[k] = vak; | ||
var o = unknownRecordValidation.value; | ||
var a = o; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var k = keys[i]; | ||
var ak = a[k]; | ||
var type_2 = props[k]; | ||
var validation = type_2.validate(ak, exports.appendContext(c, k, type_2, ak)); | ||
if (validation.isLeft() && ak !== undefined) { | ||
pushAll(errors, validation.value); | ||
} | ||
else if (validation.isRight()) { | ||
var vak = validation.value; | ||
if (vak !== ak) { | ||
/* istanbul ignore next */ | ||
if (a === o) { | ||
a = __assign({}, o); | ||
} | ||
a[k] = vak; | ||
} | ||
} | ||
return errors.length ? exports.failures(errors) : exports.success(a); | ||
} | ||
return errors.length > 0 ? exports.failures(errors) : exports.success(a); | ||
}, useIdentity(types, len) | ||
@@ -787,29 +796,27 @@ ? exports.identity | ||
}, function (u, c) { | ||
var dictionaryValidation = exports.UnknownRecord.validate(u, c); | ||
if (dictionaryValidation.isLeft()) { | ||
return dictionaryValidation; | ||
var unknownRecordValidation = exports.UnknownRecord.validate(u, c); | ||
if (unknownRecordValidation.isLeft()) { | ||
return unknownRecordValidation; | ||
} | ||
else { | ||
var o = dictionaryValidation.value; | ||
if (!isUnknownCodec(codomain) && !isAnyCodec(codomain) && !isObject(o)) { | ||
return exports.failure(u, c); | ||
var o = unknownRecordValidation.value; | ||
if (!isUnknownCodec(codomain) && !isAnyCodec(codomain) && !isObject(o)) { | ||
return exports.failure(u, c); | ||
} | ||
var a = {}; | ||
var errors = []; | ||
var keys = Object.keys(o); | ||
var len = keys.length; | ||
var changed = false; | ||
for (var i = 0; i < len; i++) { | ||
var k = keys[i]; | ||
var ok = o[k]; | ||
var domainValidation = domain.validate(k, exports.appendContext(c, k, domain, k)); | ||
if (domainValidation.isLeft()) { | ||
pushAll(errors, domainValidation.value); | ||
} | ||
var a = {}; | ||
var errors = []; | ||
var keys = Object.keys(o); | ||
var len = keys.length; | ||
var changed = false; | ||
for (var i = 0; i < len; i++) { | ||
var k = keys[i]; | ||
var ok = o[k]; | ||
var domainValidation = domain.validate(k, exports.appendContext(c, k, domain)); | ||
var codomainValidation = codomain.validate(ok, exports.appendContext(c, k, codomain)); | ||
if (domainValidation.isLeft()) { | ||
pushAll(errors, domainValidation.value); | ||
} | ||
else { | ||
var vk = domainValidation.value; | ||
changed = changed || vk !== k; | ||
k = vk; | ||
} | ||
else { | ||
var vk = domainValidation.value; | ||
changed = changed || vk !== k; | ||
k = vk; | ||
var codomainValidation = codomain.validate(ok, exports.appendContext(c, k, codomain, ok)); | ||
if (codomainValidation.isLeft()) { | ||
@@ -824,4 +831,4 @@ pushAll(errors, codomainValidation.value); | ||
} | ||
return errors.length ? exports.failures(errors) : exports.success((changed ? a : o)); | ||
} | ||
return errors.length > 0 ? exports.failures(errors) : exports.success((changed ? a : o)); | ||
}, domain.encode === exports.identity && codomain.encode === exports.identity | ||
@@ -873,16 +880,13 @@ ? exports.identity | ||
var type_3 = codecs[i]; | ||
var validation = type_3.validate(u, exports.appendContext(c, String(i), type_3)); | ||
var validation = type_3.validate(u, exports.appendContext(c, String(i), type_3, u)); | ||
if (validation.isRight()) { | ||
return validation; | ||
} | ||
else { | ||
pushAll(errors, validation.value); | ||
} | ||
pushAll(errors, validation.value); | ||
} | ||
return errors.length ? exports.failures(errors) : exports.failure(u, c); | ||
return errors.length > 0 ? exports.failures(errors) : exports.failure(u, c); | ||
}, useIdentity(codecs, len) | ||
? exports.identity | ||
: function (a) { | ||
var i = 0; | ||
for (; i < len - 1; i++) { | ||
for (var i = 0; i < len - 1; i++) { | ||
var type_4 = codecs[i]; | ||
@@ -893,3 +897,3 @@ if (type_4.is(a)) { | ||
} | ||
return codecs[i].encode(a); | ||
return a; | ||
}, codecs); | ||
@@ -911,29 +915,32 @@ }; | ||
exports.IntersectionType = IntersectionType; | ||
var mergeAll = function (us) { | ||
var r = us[0]; | ||
for (var i = 1; i < us.length; i++) { | ||
var u = us[i]; | ||
if (u !== r) { | ||
r = Object.assign(r, u); | ||
} | ||
} | ||
return r; | ||
}; | ||
function intersection(codecs, name) { | ||
if (name === void 0) { name = "(" + codecs.map(function (type) { return type.name; }).join(' & ') + ")"; } | ||
var len = codecs.length; | ||
return new IntersectionType(name, function (u) { return (len === 0 ? false : codecs.every(function (type) { return type.is(u); })); }, function (u, c) { | ||
var a = u; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var type_5 = codecs[i]; | ||
var validation = type_5.validate(a, exports.appendContext(c, String(i), type_5)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
a = validation.value; | ||
} | ||
} | ||
return errors.length ? exports.failures(errors) : len === 0 ? exports.failure(u, c) : exports.success(a); | ||
}, useIdentity(codecs, len) | ||
? exports.identity | ||
: function (a) { | ||
var s = a; | ||
return new IntersectionType(name, function (u) { return codecs.every(function (type) { return type.is(u); }); }, codecs.length === 0 | ||
? exports.success | ||
: function (u, c) { | ||
var us = []; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var type_6 = codecs[i]; | ||
s = type_6.encode(s); | ||
var codec = codecs[i]; | ||
var validation = codec.validate(u, exports.appendContext(c, String(i), codec, u)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
us.push(validation.value); | ||
} | ||
} | ||
return s; | ||
}, codecs); | ||
return errors.length > 0 ? exports.failures(errors) : exports.success(mergeAll(us)); | ||
}, codecs.length === 0 ? exports.identity : function (a) { return mergeAll(codecs.map(function (codec) { return codec.encode(a); })); }, codecs); | ||
} | ||
@@ -959,33 +966,28 @@ exports.intersection = intersection; | ||
return new TupleType(name, function (u) { return exports.UnknownArray.is(u) && u.length === len && codecs.every(function (type, i) { return type.is(u[i]); }); }, function (u, c) { | ||
var arrayValidation = exports.UnknownArray.validate(u, c); | ||
if (arrayValidation.isLeft()) { | ||
return arrayValidation; | ||
var unknownArrayValidation = exports.UnknownArray.validate(u, c); | ||
if (unknownArrayValidation.isLeft()) { | ||
return unknownArrayValidation; | ||
} | ||
else { | ||
var as = arrayValidation.value; | ||
var t = as; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var a = as[i]; | ||
var type_7 = codecs[i]; | ||
var validation = type_7.validate(a, exports.appendContext(c, String(i), type_7)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
var va = validation.value; | ||
if (va !== a) { | ||
/* istanbul ignore next */ | ||
if (t === as) { | ||
t = as.slice(); | ||
} | ||
t[i] = va; | ||
var us = unknownArrayValidation.value; | ||
var as = us.length > len ? us.slice(0, len) : us; // strip additional components | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var a = us[i]; | ||
var type_5 = codecs[i]; | ||
var validation = type_5.validate(a, exports.appendContext(c, String(i), type_5, a)); | ||
if (validation.isLeft()) { | ||
pushAll(errors, validation.value); | ||
} | ||
else { | ||
var va = validation.value; | ||
if (va !== a) { | ||
/* istanbul ignore next */ | ||
if (as === us) { | ||
as = us.slice(); | ||
} | ||
as[i] = va; | ||
} | ||
} | ||
if (as.length > len) { | ||
errors.push({ value: as[len], context: exports.appendContext(c, String(len), exports.never) }); | ||
} | ||
return errors.length ? exports.failures(errors) : exports.success(t); | ||
} | ||
return errors.length > 0 ? exports.failures(errors) : exports.success(as); | ||
}, useIdentity(codecs, len) ? exports.identity : function (a) { return codecs.map(function (type, i) { return type.encode(a[i]); }); }, codecs); | ||
@@ -1047,5 +1049,3 @@ } | ||
} | ||
else { | ||
return x; | ||
} | ||
return x; | ||
}); | ||
@@ -1056,2 +1056,3 @@ }, arrayType.encode, codec); | ||
* @since 1.0.0 | ||
* @deprecated | ||
*/ | ||
@@ -1070,10 +1071,7 @@ var StrictType = /** @class */ (function (_super) { | ||
/** | ||
* Specifies that only the given properties are allowed | ||
* @deprecated use `exact` instead | ||
* Strips additional properties | ||
* @since 1.0.0 | ||
*/ | ||
exports.strict = function (props, name) { | ||
if (name === void 0) { name = "StrictType<" + getNameFromProps(props) + ">"; } | ||
var exactType = exact(exports.type(props)); | ||
return new StrictType(name, exactType.is, exactType.validate, exactType.encode, props); | ||
return exports.exact(exports.type(props), name); | ||
}; | ||
@@ -1088,17 +1086,15 @@ /** @internal */ | ||
} | ||
else if (b === monoidIndexRecord.empty) { | ||
if (b === monoidIndexRecord.empty) { | ||
return a; | ||
} | ||
else { | ||
var r = cloneIndexRecord(a); | ||
for (var k in b) { | ||
if (r.hasOwnProperty(k)) { | ||
(_a = r[k]).push.apply(_a, b[k]); | ||
} | ||
else { | ||
r[k] = b[k]; | ||
} | ||
var r = cloneIndexRecord(a); | ||
for (var k in b) { | ||
if (r.hasOwnProperty(k)) { | ||
(_a = r[k]).push.apply(_a, b[k]); | ||
} | ||
return r; | ||
else { | ||
r[k] = b[k]; | ||
} | ||
} | ||
return r; | ||
}, | ||
@@ -1145,12 +1141,12 @@ empty: exports.emptyIndexRecord | ||
} | ||
else if (isIntersectionCodec(codec)) { | ||
if (isIntersectionCodec(codec)) { | ||
return foldMapIndexRecord(codec.types, function (type) { return getCodecIndexRecord(type, origin, codec); }); | ||
} | ||
else if (isUnionCodec(codec)) { | ||
if (isUnionCodec(codec)) { | ||
return foldMapIndexRecord(codec.types, function (type) { return getCodecIndexRecord(type, origin, type); }); | ||
} | ||
else if (isExactCodec(codec) || isRefinementCodec(codec)) { | ||
if (isExactCodec(codec) || isRefinementCodec(codec)) { | ||
return getCodecIndexRecord(codec.type, origin, codec); | ||
} | ||
else if (isRecursiveCodec(codec)) { | ||
if (isRecursiveCodec(codec)) { | ||
var indexRecord = codec.getIndexRecord(); | ||
@@ -1162,5 +1158,3 @@ if (codec !== origin) { | ||
} | ||
else { | ||
return monoidIndexRecord.empty; | ||
} | ||
return monoidIndexRecord.empty; | ||
}; | ||
@@ -1229,15 +1223,11 @@ var isRecursiveCodecIndexable = true; | ||
}); | ||
var find = function (tagValue) { | ||
var findIndex = function (tagValue) { | ||
for (var i = 0; i < indexWithPosition.length; i++) { | ||
var _a = indexWithPosition[i], value = _a[0], position = _a[1]; | ||
if (value === tagValue) { | ||
return [i, codecs[position]]; | ||
return position; | ||
} | ||
} | ||
}; | ||
var isTagValue = function (u) { return find(u) !== undefined; }; | ||
var TagValue = new Type(index.map(function (_a) { | ||
var v = _a[0]; | ||
return JSON.stringify(v); | ||
}).join(' | '), isTagValue, function (u, c) { return (isTagValue(u) ? exports.success(u) : exports.failure(u, c)); }, exports.identity); | ||
var isTagValue = function (u) { return findIndex(u) !== undefined; }; | ||
return new TaggedUnionType(name, function (u) { | ||
@@ -1248,4 +1238,4 @@ if (!exports.UnknownRecord.is(u)) { | ||
var tagValue = u[tag]; | ||
var type = find(tagValue); | ||
return type ? type[1].is(u) : false; | ||
var index = findIndex(tagValue); | ||
return index !== undefined ? codecs[index].is(u) : false; | ||
}, function (u, c) { | ||
@@ -1256,13 +1246,11 @@ var dictionaryResult = exports.UnknownRecord.validate(u, c); | ||
} | ||
else { | ||
var d = dictionaryResult.value; | ||
var tagValue = d[tag]; | ||
var tagValueValidation = TagValue.validate(d[tag], exports.appendContext(c, tag, TagValue)); | ||
if (tagValueValidation.isLeft()) { | ||
return tagValueValidation; | ||
} | ||
var _a = find(tagValue), i = _a[0], type_8 = _a[1]; | ||
return type_8.validate(d, exports.appendContext(c, String(i), type_8)); | ||
var d = dictionaryResult.value; | ||
var tagValue = d[tag]; | ||
if (!isTagValue(tagValue)) { | ||
return exports.failure(u, c); | ||
} | ||
}, useIdentity(codecs, len) ? exports.identity : function (a) { return find(a[tag])[1].encode(a); }, codecs, tag); | ||
var index = findIndex(tagValue); | ||
var codec = codecs[index]; | ||
return codec.validate(d, exports.appendContext(c, String(index), codec, d)); | ||
}, useIdentity(codecs, len) ? exports.identity : function (a) { return codecs[findIndex(a[tag])].encode(a); }, codecs, tag); | ||
}; | ||
@@ -1296,5 +1284,3 @@ /** | ||
} | ||
else { | ||
return getTaggedUnion(indexRecord[tag], tag, codecs, name); | ||
} | ||
return getTaggedUnion(indexRecord[tag], tag, codecs, name); | ||
}; | ||
@@ -1328,29 +1314,45 @@ /** | ||
}; | ||
var stripKeys = function (o, props) { | ||
var keys = Object.getOwnPropertyNames(o); | ||
var shouldStrip = false; | ||
var r = {}; | ||
for (var i = 0; i < keys.length; i++) { | ||
var key = keys[i]; | ||
if (!hasOwnProperty.call(props, key)) { | ||
shouldStrip = true; | ||
} | ||
else { | ||
r[key] = o[key]; | ||
} | ||
} | ||
return shouldStrip ? r : o; | ||
}; | ||
var getExactTypeName = function (codec) { | ||
if (isInterfaceCodec(codec)) { | ||
return "{| " + getNameFromProps(codec.props) + " |}"; | ||
} | ||
else if (isPartialCodec(codec)) { | ||
return getPartialTypeName("{| " + getNameFromProps(codec.props) + " |}"); | ||
} | ||
return "Exact<" + codec.name + ">"; | ||
}; | ||
/** | ||
* Strips additional properties | ||
* @since 1.1.0 | ||
*/ | ||
function exact(codec, name) { | ||
if (name === void 0) { name = "ExactType<" + codec.name + ">"; } | ||
exports.exact = function (codec, name) { | ||
if (name === void 0) { name = getExactTypeName(codec); } | ||
var props = getProps(codec); | ||
return new ExactType(name, function (u) { return codec.is(u) && Object.getOwnPropertyNames(u).every(function (k) { return hasOwnProperty.call(props, k); }); }, function (u, c) { | ||
var looseValidation = codec.validate(u, c); | ||
if (looseValidation.isLeft()) { | ||
return looseValidation; | ||
var unknownRecordValidation = exports.UnknownRecord.validate(u, c); | ||
if (unknownRecordValidation.isLeft()) { | ||
return unknownRecordValidation; | ||
} | ||
else { | ||
var o = looseValidation.value; | ||
var keys = Object.getOwnPropertyNames(o); | ||
var len = keys.length; | ||
var errors = []; | ||
for (var i = 0; i < len; i++) { | ||
var key = keys[i]; | ||
if (!hasOwnProperty.call(props, key)) { | ||
errors.push({ value: o[key], context: exports.appendContext(c, key, exports.never) }); | ||
} | ||
} | ||
return errors.length ? exports.failures(errors) : exports.success(o); | ||
var validation = codec.validate(u, c); | ||
if (validation.isLeft()) { | ||
return validation; | ||
} | ||
}, codec.encode, codec); | ||
} | ||
exports.exact = exact; | ||
return exports.success(stripKeys(validation.value, props)); | ||
}, function (a) { return codec.encode(stripKeys(a, props)); }, codec); | ||
}; | ||
/** | ||
@@ -1357,0 +1359,0 @@ * Drops the codec "kind" |
@@ -5,3 +5,9 @@ "use strict"; | ||
function stringify(v) { | ||
return typeof v === 'function' ? index_1.getFunctionName(v) : JSON.stringify(v); | ||
if (typeof v === 'function') { | ||
return index_1.getFunctionName(v); | ||
} | ||
if (typeof v === 'number' && !isFinite(v)) { | ||
return v > 0 ? 'Infinity' : '-Infinity'; | ||
} | ||
return JSON.stringify(v); | ||
} | ||
@@ -8,0 +14,0 @@ function getContextPath(context) { |
{ | ||
"name": "io-ts", | ||
"version": "1.7.1", | ||
"version": "1.8.0", | ||
"description": "TypeScript compatible runtime type system for IO validation", | ||
@@ -42,3 +42,3 @@ "files": [ | ||
"benchmark": "2.1.4", | ||
"dtslint": "^0.4.2", | ||
"dtslint": "github:gcanti/dtslint", | ||
"jest": "^23.6.0", | ||
@@ -45,0 +45,0 @@ "mocha": "^5.2.0", |
121
README.md
@@ -208,31 +208,32 @@ [![build status](https://img.shields.io/travis/gcanti/io-ts/master.svg?style=flat-square)](https://travis-ci.org/gcanti/io-ts) | ||
| Type | TypeScript | codec / combinator | | ||
| ----------------- | --------------------------------------- | ----------------------------------------------------- | | ||
| null | `null` | `t.null` or `t.nullType` | | ||
| undefined | `undefined` | `t.undefined` | | ||
| void | `void` | `t.void` or `t.voidType` | | ||
| string | `string` | `t.string` | | ||
| number | `number` | `t.number` | | ||
| boolean | `boolean` | `t.boolean` | | ||
| unknown | `unknown` | t.unknown | | ||
| never | `never` | `t.never` | | ||
| object | `object` | `t.object` | | ||
| integer | ✘ | `t.Integer` | | ||
| array of unknown | `Array<unknown>` | `t.UnknownArray` | | ||
| array of type | `Array<A>` | `t.array(A)` | | ||
| record of unknown | `Record<string, unknown>` | `t.UnknownRecord` | | ||
| record of type | `Record<K, A>` | `t.record(K, A)` | | ||
| function | `Function` | `t.Function` | | ||
| literal | `'s'` | `t.literal('s')` | | ||
| partial | `Partial<{ name: string }>` | `t.partial({ name: t.string })` | | ||
| readonly | `Readonly<T>` | `t.readonly(T)` | | ||
| readonly array | `ReadonlyArray<number>` | `t.readonlyArray(t.number)` | | ||
| type alias | `type A = { name: string }` | `t.type({ name: t.string })` | | ||
| tuple | `[ A, B ]` | `t.tuple([ A, B ])` | | ||
| union | `A \| B` | `t.union([ A, B ])` or `t.taggedUnion(tag, [ A, B ])` | | ||
| intersection | `A & B` | `t.intersection([ A, B ])` | | ||
| keyof | `keyof M` | `t.keyof(M)` | | ||
| recursive types | see [Recursive types](#recursive-types) | `t.recursion(name, definition)` | | ||
| refinement | ✘ | `t.refinement(A, predicate)` | | ||
| exact types | ✘ | `t.exact(type)` | | ||
| Type | TypeScript | codec / combinator | | ||
| --------------------------- | --------------------------- | -------------------------------------------------------------------- | | ||
| null | `null` | `t.null` or `t.nullType` | | ||
| undefined | `undefined` | `t.undefined` | | ||
| void | `void` | `t.void` or `t.voidType` | | ||
| string | `string` | `t.string` | | ||
| number | `number` | `t.number` | | ||
| boolean | `boolean` | `t.boolean` | | ||
| unknown | `unknown` | `t.unknown` | | ||
| never | `never` | `t.never` | | ||
| object | `object` | `t.object` | | ||
| array of unknown | `Array<unknown>` | `t.UnknownArray` | | ||
| array of type | `Array<A>` | `t.array(A)` | | ||
| record of unknown | `Record<string, unknown>` | `t.UnknownRecord` | | ||
| record of type | `Record<K, A>` | `t.record(K, A)` | | ||
| function | `Function` | `t.Function` | | ||
| literal | `'s'` | `t.literal('s')` | | ||
| partial | `Partial<{ name: string }>` | `t.partial({ name: t.string })` | | ||
| readonly | `Readonly<A>` | `t.readonly(A)` | | ||
| readonly array | `ReadonlyArray<A>` | `t.readonlyArray(A)` | | ||
| type alias | `type T = { name: A }` | `t.type({ name: A })` | | ||
| tuple | `[ A, B ]` | `t.tuple([ A, B ])` | | ||
| union | `A \| B` | `t.union([ A, B ])` or `t.taggedUnion(tag, [ A, B ])` | | ||
| intersection | `A & B` | `t.intersection([ A, B ])` | | ||
| keyof | `keyof M` | `t.keyof(M)` | | ||
| recursive types | ✘ | `t.recursion(name, definition)` | | ||
| branded types / refinements | ✘ | `t.brand(A, predicate, brand)` | | ||
| integer | ✘ | `t.Int` (built-in branded codec) | | ||
| exact types | ✘ | `t.exact(type)` | | ||
| strict | ✘ | `t.strict({ name: A })` (an alias of `t.exact(t.type({ name: A })))` | | ||
@@ -307,34 +308,44 @@ # Recursive types | ||
# Refinements | ||
# Branded types / Refinements | ||
You can refine a type (_any_ type) using the `refinement` combinator | ||
You can refine a codec (_any_ codec) using the `brand` combinator | ||
```ts | ||
const Positive = t.refinement(t.number, n => n >= 0, 'Positive') | ||
const Positive = t.brand(t.number, n => n >= 0, 'Positive') | ||
const Adult = t.refinement(Person, person => person.age >= 18, 'Adult') | ||
``` | ||
type Positive = t.TypeOf<typeof Positive> | ||
/* | ||
same as | ||
type Positive = number & t.Brand<"Positive"> | ||
*/ | ||
# Exact types | ||
const PositiveInt = t.intersection([t.Int, Positive]) | ||
You can make a codec alias exact (which means that only the given properties are allowed) using the `exact` combinator | ||
type PositiveInt = t.TypeOf<typeof PositiveInt> | ||
/* | ||
same as | ||
type PositiveInt = number & t.Brand<"Int"> & t.Brand<"Positive"> | ||
*/ | ||
```ts | ||
const Person = t.type({ | ||
name: t.string, | ||
age: t.number | ||
age: PositiveInt | ||
}) | ||
const ExactPerson = t.exact(Person) | ||
const Adult = t.brand(Person, person => person.age >= 18, 'Adult') | ||
Person.decode({ name: 'Giulio', age: 43, surname: 'Canti' }) // ok | ||
ExactPerson.decode({ name: 'Giulio', age: 43, surname: 'Canti' }) // fails | ||
type Adult = t.TypeOf<typeof Adult> | ||
/* | ||
same as | ||
type Adult = { | ||
name: string; | ||
age: number & t.Brand<"Int"> & t.Brand<"Positive">; | ||
} & t.Brand<"Adult"> | ||
*/ | ||
``` | ||
# Strict types (deprecated) | ||
# Exact types | ||
**Note**. This combinator is deprecated, use `exact` instead. | ||
You can make a codec exact (which means that additional properties are stripped) using the `exact` combinator | ||
You can make a codec strict (which means that only the given properties are allowed) using the `strict` combinator | ||
```ts | ||
@@ -346,6 +357,6 @@ const Person = t.type({ | ||
const StrictPerson = t.strict(Person.props) | ||
const ExactPerson = t.exact(Person) | ||
Person.decode({ name: 'Giulio', age: 43, surname: 'Canti' }) // ok | ||
StrictPerson.decode({ name: 'Giulio', age: 43, surname: 'Canti' }) // fails | ||
Person.decode({ name: 'Giulio', age: 43, surname: 'Canti' }) // ok, result is right({ name: 'Giulio', age: 43, surname: 'Canti' }) | ||
ExactPerson.decode({ name: 'Giulio', age: 43, surname: 'Canti' }) // ok but result is right({ name: 'Giulio', age: 43 }) | ||
``` | ||
@@ -405,9 +416,9 @@ | ||
// represents a Date from an ISO string | ||
const DateFromString = new t.Type<Date, string>( | ||
const DateFromString = new t.Type<Date, string, unknown>( | ||
'DateFromString', | ||
(m): m is Date => m instanceof Date, | ||
(m, c) => | ||
t.string.validate(m, c).chain(s => { | ||
(u): u is Date => u instanceof Date, | ||
(u, c) => | ||
t.string.validate(u, c).chain(s => { | ||
const d = new Date(s) | ||
return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d) | ||
return isNaN(d.getTime()) ? t.failure(u, c) : t.success(d) | ||
}), | ||
@@ -455,2 +466,3 @@ a => a.toISOString() | ||
}) | ||
const Links = t.interface({ | ||
@@ -553,3 +565,2 @@ previous: t.string, | ||
- unique check for free | ||
- better performance | ||
- quick info stays responsive | ||
- better performance, `O(log(n))` vs `O(n)` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
104693
2310
561