rtti.js is deprecated.
rtti.js is renamed to vanilla-schema-validator on Dec 27 2022. This documentation is kept for historical reason. Use vanilla-schema-validator for new projects.
rtti.js is a non-opinionated simple convention to determine a type of an object in JavaScript.
import { rtti } from 'rtti.js';
console.error( rtti.string()( 42 )); // false
console.error( rtti.string()( '42' )); // true
console.error( rtti.number()( 42 )); // true
console.error( rtti.number()( '42' )); // false
Combining these functions enables you to validate more complex objects :
import { rtti } from 'rtti.js';
const t_person = rtti.object({
name : rtti.string(),
age : rtti.number(),
visited : rtti.boolean(),
});
const obj1 = {
name :'John',
age : 42,
visited : true,
};
console.error( t_person( obj1 ) ); // true
The functions that are defined in rtti are merely factories of various
validators. In the example above, rtti.string
and rtti.number
are factories
of validators.
They are merely utilities and not requirement; you can create a validator manually on the fly. For example, the following example works, too:
import { rtti } from 'rtti.js';
const t_person = rtti.object({
name : rtti.string(),
age : rtti.number(),
visited : rtti.boolean(),
since : (o)=>o instanceof Date,
});
const obj1 = {
name :'John',
age : 42,
visited : true,
since : new Date('24 Jan 1986 17:58:24 -0700'),
};
console.error( t_person( obj1 ) ); // true
const obj2 = {
name :'John',
age : 42,
visited : true,
since : { is_wrong_date : true }
};
console.error( t_person( obj2 ) ); // false
Basic concept of this convention is quit simple and with this convention, you can accomplish validation in most cases without these complicated frameworks.
The desigin concept of rtti.js is based on my hypothesis explains that in
JavaScript, it is impossible to precisely determine a type of an object via its
run-time type information and duck typing
is the only way to accomplish it.
A Type in JavaScript is merely the least expectation to an object. For example,
if you get an object, you might expect that there is a property which name is
product_id
and as long as there is the property, your code will work as you
expected; otherwise it won't. That is the least expectation to an object.
Its design goal is to exhaustively determine a type of an object in the sense of described above, with the maximum coverage of those various corner cases which occur caused via ambiguously defined JavaScript type system.
Especially the first concern of rtti.js
is by no means readability; if you
expect those sweet syntax suger with function chaining, this is not for you.
The convention of rtti.js
recommends the type determinors are formed by the
following three elements.
rtti.string()('value')
1 2 3
-
Namespace
... We call this partNamespace
. A namespace object keeps a number ofFactory
which is explained in 2. -
Factory
... We call this partFactory
. AFactory
is a function to create aValidator
which is explained in 3. -
Validator
... We call this partValidator
. AValidator
is a function which returnstrue
if the given value is as expected; otherwise returnsfalse
.
If you define a factory of a validator as following :
rtti.hello_validator = ()=>(o)=>o === 'hello';
you can use the validator as following:
rtti.hello_validator()( 'hello' ) // returns true
prevent-undefined is a debugging tool that prevents generating undefined
values via accessing properties by incorrect property names.
prevent-undefined
supports the convention of rtti.js
.
The way to use prevent-undefined with rtti.js is as following:
const t_person_info = rtti.object({
name : rtti.string(),
age : rtti.number(),
});
const preventUndefined = require('prevent-undefined');
const personInfo = getPersonInfoFromSomewhere();
const protectedPersonInfo = preventUndefined( personInfo, t_person_info() );
console.error( protectedPersonInfo.non_existent_prop ); // throws an error
protectedPersonInfo.age = 'an invalid number' ; // throws an error
For further information, see prevent-undefined.
rtti.js
offers some basic validators as default. These validators are there
only for your convenience; again, it is not mandatory to use them as long as
the functions you offer are following the rtti.js
's convention.
Available validators are:
- undefined()
- null()
- boolean()
- number()
- string()
- bigint()
- symbol()
- function()
- any()
- or()
- and()
- not()
- object()
- array()
- equals()
- uuid()
Their usage may be self-descriptive; though, some of them should be explaind.
Returns true
if typeof
operator to the given value returns undefined
; otherwise returns false
.
rtti.undefined()( undefined ) // returns true
rtti.undefined()( null ) // returns false
Returns true
if the given value is strictly equal to null
value; otherwise returns false
.
rtti.null()( null ) // returns true
rtti.null()( 1 ) // returns false
Returns true
if typeof
operator to the given value returns boolean
; otherwise returns false
.
rtti.boolean()( false ) // returns true
rtti.boolean()( true ) // returns true
rtti.boolean()( 'true' ) // returns false
Returns true
if typeof
operator to the given value returns number
; otherwise returns false
.
rtti.number()( 42 ) // returns true
rtti.number()('42') // returns false
Returns true
if typeof
operator to the given value returns string
; otherwise returns false
.
rtti.string()( '42' ) // returns true
rtti.string()( 42 ) // returns false
Returns true
if typeof
operator to the given value returns bigint
; otherwise returns false
.
rtti.bigint()( BigInt(42) ) // returns true
rtti.bigint()( 42 ) // returns false
Returns true
if typeof
operator to the given value returns symbol
; otherwise returns false
.
rtti.symbol()( Symbol('hello') ) // returns true
rtti.symbol()( Symbol.for('hello') ) // returns true
rtti.symbol()( 'hello' ) // returns false
Returns true
if typeof
operator to the given value returns function
; otherwise returns false
.
rtti.function()( ()=>{} ) // returns true
rtti.function()( function(){} ) // returns true
rtti.function()( new Function()) // returns true
rtti.function()( 'function' ) // returns false
any()
always return true
no matter which type of a value is specified as a
parameter.
rtti.any()( '123' ); // returns true
rtti.any()( 123 ); // returns true
rtti.any()( true ); // returns true
or()
calls specified validators from left to right and returns true
if at
least one of the validators return true
.
rtti.or( rtti.string(), rtti.number())( '123' ); // returns true
rtti.or( rtti.string(), rtti.number())( 123 ); // returns true
rtti.or( rtti.string(), rtti.number())( true ); // returns false
and()
calls specified validators from left to right and return true
if and only if
all of the specified validators return true
; otherwise returns false
.
rtti.and( rtti.number() , (v)=>100<v )( 200 ); // returns true
rtti.and( rtti.number() , (v)=>100<v )( 50 ); // returns false
not()
negates the result of the specified validator.
rtti.not( rtti.number() )( 100 ); // returns false
rtti.not( rtti.number() )( '100' ); // returns true
object()
checks the validity of the given object. object()
receives objects
as its parameters and takes them as definition of the object properties and
create a validator.
The definition objects should contain validators as their properties and these validators are to be called when the validator performs comparison.
The validator will scan all properties which defined in the definition objects, then call them with corresponding property values on the object to be compared.
the validator returns true
if and only if all of the validators returns true
;
otherwise, returns false
.
const t = rtti.object({
foo : rtti.number(),
bar : rtti.string(),
});
t({
}); // returns false
t({
foo: 100,
bar: "100",
}); // returns true
array()
takes a number of validators as arguments, then, at the validation,
invokes each validator with its corresponding element in the target array
object. If the all validators return true
, array()
returns true
;
otherwise returns false
.
If the number of elements in the target array is not equal to the number of
specified validators, this validator returns false
. v1.0.0
const validator = rtti.statement`
array(
equals( <<'a'>> ),
equals( <<'b'>> ),
equals( <<'c'>> ),
)`();
console.log( validator(['a','b','c']) ); // true
console.log( validator(['a','b','d']) ); // false
console.log( validator(['a','b','c', 'd' ])); //true
console.log( validator(['a','b' ])); // false
- Prior to v1.0.0, this validator was refererred as
array_of()
. - Prior to v1.0.0, this validator did not check the number of elements :
If the number of elements in the target array is greater than the number of the specified validators,
array()
ignores the remaining elements.If the number of elements in the target array object is less than the number of validators given in the parameter, this validator returns
false
.
array_of()
checks if all of the elements of the given array object conform to a
specified validator. array_of()
receives a validator and call it with the all of
the elements on the specified array object. Return true
if all elements conform
to the validator; otherwise return false
.
rtti.array_of(rtti.number())([1,2,3]); // return true
rtti.array_of(rtti.number())([1,2,'3']); // return false
rtti.array_of(rtti.or( rtti.string(), rtti.number()))([1,2,'3']); // return true
- Prior to v1.0.0, this validator was refererred as
array()
.
equals()
takes a parameter as a target value and creates a validator which
compares with the target value. The validator returns true
if and only if
the given value is strictly equal to the target value.
rtti.equals(1)(1); // true
rtti.equals(1)('1'); // false
uuid()
checks if the given value conforms to the specification of uuid.
rtti.uuid()( '2a945d9d-2cfb-423b-afb2-362ea7c37e67' ) // true
rtti.uuid()( 'hello' ) // false
rtti.uuid()( '2a945d9d-2cfb-423b-afb2-362ea7m37e67' ) // false
rtti.uuid()( '2a945d9d-2cfb-423b-afb2-362ea7c37e677' ) // false
rtti.uuid()( '2a945d9d-2cfb423b-afb2-362ea7c37e677' ) // false
uuid()
checks if the given value is a string; returns false
if the given
value is not a string.
rtti.uuid()( 1 ) // false
rtti.uuid()( false ) // false
The rtti
offers a template literal function which is called RTTI Statement
Script Compiler. Statement compiler helps to build various validators:
const type = rtti.statement`
object(
foo : number(),
bar : string(),
)
`();
const v = {
foo:42,
bar:'hello',
};
console.error( type( v ) ); // true;
const v2 = {
foo: false,
bar: BigInt(1),
};
console.error( type( v2 ) ); // false;
In this document, sometimes a reference to rtti
object is called namespace
.
In JavaScript, in order to build complex validators, it is necessary to specify
a desired namespace reference everytime you refer the validator factories. In
RTTI Statement Script, it is possible to omit the namespace specifier.
The statement compiler may help you to build your validators with less boilerplate.
a note for backward compatibility : former to v0.1.2, rtti
object can be
used as a template literal function. This behavior is deprecated. Though it is
still available to be used as a template literal, this will be removed in the
future version. The new project should not rely on this behavior.
In the statemet string, regions surrounded by <<
and `>>`` are treated as
raw JavaScript values.
For example,
const type = rtti.statement`
object(
foo : equals( << 42 >> ),
bar : equals( << '42' >> ),
)
`;
is loosely compiled to
```javascript
const type = rtti.object({
foo : rtti.equals( 42 ),
bar : rtti.equals( '42' ),
})
You can add your own validators by setting factorys of your desired validators
as properties on the rtti
object.
const type = rtti.statement`
object(
foo : Foo(),
bar : Bar(),
)
`();
rtti.Foo = (...defs)=>(o)=>typeof o ==='number';
rtti.Bar = (...defs)=>(o)=>typeof o ==='string';
const v = {
foo:42,
bar:'hello',
};
console.error( type( v ) ); // true;
Correction in v0.1.6
, this part has been corrected. It is necessary to
set factories of validators, not validators themself.
You usually don't want to set your own evaluators to the global rtti
object
because setting to the global rtti
object causes id confliction with the
other projects. In order to avoid confliction, you can create your own rtti
object by clone()
method.
import { rtti } from 'rtti.js';
const rtti2 = rtti.clone();
rtti2.Foo = (...defs)=>(o)=>typeof o ==='number';
rtti2.Bar = (...defs)=>(o)=>typeof o ==='string';
const type2 = rtti2.statement`
object(
foo : Foo(),
bar : Bar(),
)
`();
const v = {
foo:42,
bar:'hello',
};
console.error( type2( v ) ); // true;
const type1 = rtti.statement`
object(
foo : Foo(),
bar : Bar(),
)
`();
console.error( type1( v ) ); // error;
make_vali_factory
is a helper function to create a reliable validator function:
const INFO = Symbol.for( 'dump rtti.js information' );
const create_info_gen_from_string = ( info_gen_string )=>{
if ( typeof info_gen_string === 'string' ) {
return ()=>info_gen_string;
} else {
throw new TypeError('found an invalid argument');
}
};
const make_vali_factory = ( vali_gen, info_gen=(...defs)=>"unknown", chk_args=(...defs)=>{} )=>{
if ( typeof info_gen === 'string' ) {
info_gen = create_info_gen_from_string( info_gen );
}
return (...defs)=>{
chk_args(...defs);
const vali = vali_gen(...defs);
const info = info_gen(...defs);
return (o)=>o=== INFO ? info : vali(o);
}
};
-
vali_gen
is a function to create the evaluator. -
info_gen
is a function to create a string value to express the type name; can also be a string. -
chk_args
is a function which offers a chance to check the arguments.
The following example implements a null checker.
const null_checker = make_vali_factory(
// a closure that does the evaluation
(...defs)=>(o)=>o === null
// a closure that returns the name of the type
(...defs)=>"null",
// null checker takes no argument
(...defs)=>{
if ( defs.length !== 0 ) {
throw new RangeError( 'no definition can be specified' );
}
},
);
At the version v0.1.5 makeValiFactory()
was renamed to
make_vali_factory()
. Even though makeValiFactory()
is still available, new
projects should not use it.
At the version v1.0.0 the identifiers array()
and array_of()
are
renamed so that array_of
becomes array
and array()
becomes array_of()
for the sake of naming consistency.
-
v0.1.0 released
-
v0.1.1 added
uuid()
equals()
-
v0.1.2 added
clone()
; the template literal function asrtti.statement
-
v0.1.3 added
any()
-
v0.1.4 added << >> blocks.
-
v0.1.5 statement compiler switches namespaces depends on how the validator factory is called.
-
v0.1.6 added
array_of()
validator. some document correction is also done. (Thu, 17 Nov 2022 16:44:33 +0900) -
v0.1.7 more informative error messages (Fri, 18 Nov 2022 11:56:11 +0900)
-
v0.1.8 more informative error messages (Fri, 18 Nov 2022 17:32:01 +0900)
-
v1.0.0 The identifiers
array()
andarray_of()
are swapped. Nowarray()
is calledarray_of()
whilearray_of()
is calledarray()
. This breaks backward compatibility. -
v1.0.1 Fixed the broken
array()
validator . -
v1.0.2 Fixed
README.md
.
This documentation is not perfect and there are still a lot of things which should be on this document.
Thank you very much for your attention.
Atsushi Oka / I'm from Tokyo. For further information, see my github account.