1.0.4 • Published 1 year ago

rtti.js v1.0.4

Weekly downloads
-
License
ISC
Repository
github
Last release
1 year ago

rtti.js

rtti.js is deprecated.

Use vanilla-schema-validator

Documentation

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.

Design Goal of rtti.js

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 Basic Rules of the Convention of rtti.js

The convention of rtti.js recommends the type determinors are formed by the following three elements.

  rtti.string()('value')
   1      2       3
  1. Namespace ... We call this part Namespace . A namespace object keeps a number of Factory which is explained in 2.
  2. Factory ... We call this part Factory. A Factory is a function to create a Validator which is explained in 3.
  3. Validator ... We call this part Validator. A Validator is a function which returns true if the given value is as expected; otherwise returns false.

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

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.

Basic Validators

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.

undefined()

Returns true if typeof operator to the given value returns undefined; otherwise returns false.

rtti.undefined()( undefined ) // returns true
rtti.undefined()( null      ) // returns false

null()

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

boolean()

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

number()

Returns true if typeof operator to the given value returns number; otherwise returns false.

rtti.number()( 42 ) // returns true
rtti.number()('42') // returns false

string()

Returns true if typeof operator to the given value returns string; otherwise returns false.

rtti.string()( '42' ) // returns true
rtti.string()(  42  ) // returns false

bigint()

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

symbol()

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

function()

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()

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()

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()

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()

not() negates the result of the specified validator.

rtti.not( rtti.number() )(  100  ); // returns false
rtti.not( rtti.number() )( '100' ); // returns true

object()

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()

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 
Compatibility Note
  1. Prior to v1.0.0, this validator was refererred as array_of().
  2. 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()

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
Compatibility Note
  1. Prior to v1.0.0, this validator was refererred as array().

equals()

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()

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

Create Validators via RTTI Statement Script Compiler

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.

JavaScript Values in Statement Compiler

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' ),
})

Extending Template Literal Validator Builder

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.

Create Your Own Namespace for rtti.js

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()

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);
    }
  };

The Definition of the Parameters

  • 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.

Example

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' );
      }
    }, 
  );

Compatibility Note

makeValiFactory()

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.

Renamed array() and array_of()

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.

History

  • v0.1.0 released
  • v0.1.1 added uuid() equals()
  • v0.1.2 added clone(); the template literal function as rtti.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() and array_of() are swapped. Now array() is called array_of() while array_of() is called array(). This breaks backward compatibility.

  • v1.0.1 Fixed the broken array() validator .

  • v1.0.2 Fixed README.md.

Conclusion

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.

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago

0.1.8

1 year ago

0.1.7

1 year ago

0.1.6

1 year ago

0.1.5

1 year ago

0.1.4

1 year ago

0.1.3

1 year ago

0.1.2

1 year ago

0.1.1

1 year ago

0.1.0

2 years ago