@eveble/matchist

Pattern matcher for matching the types and structure of variables

Usage no npm install needed!

<script type="module">
  import evebleMatchist from 'https://cdn.skypack.dev/@eveble/matchist';
</script>

README

Eveble Matchist CircleCI

Pattern matcher useful for matching types and structure of variables.

Table of Contents

Installation

Using yarn:

yarn install @eveble/matchist

Usage

In Node.js:

Import the library

// Cherry-pick methods for smaller bundles(like rollup).
import {
  match,
  isMatching,
  any,
  integer,
  objectIncluding,
  maybe,
  optional,
  oneOf,
  where,
  MatchError,
  InvalidDefinitionError,
} from '@eveble/matchist';

Simple definitions

You can define patterns by simply specifying required value type or expected content:

Type

match(null, null); // true
match(undefined, undefined); // true
match('foo', String); // true
match(1, Number); // true
match(true, Boolean); // true
match(new Date('December 17, 1995 03:24:00'), Date); // true
match(/foo/, RegExp); // true
match(() => {
  return 'foo';
}, Function); // true
match(['foo', 1, true], Array); // true
match(['foo'], [String]); // true
match(['foo', 1], [String, Number]); // true
match({ foo: 'bar' }, Object); // true
match({ foo: 'bar' }, { foo: String }); // true
match({ foo: 'bar', baz: 1 }, { foo: String, baz: Number }); // true
class FooClass {}
match(new FooClass(), FooClass); // true
match([new FooClass()], [FooClass]); // true
match({ foo: new FooClass() }, { foo: FooClass }); // true

Equality

match('foo', 'foo'); // true
match(1, 1); // true
match(true, true); // true
match(
  new Date('December 17, 1995 03:24:00'),
  new Date('December 17, 1995 03:24:00')
); // true
match('foo', /foo/); // true
match(['foo'], ['foo']); // true
match(['foo', 1], ['foo', 1]); // true
match(['foo', 'bar'], ['foo', 'bar']); // true
match({ foo: 'bar', baz: 1 }, { foo: 'bar', baz: 1 }); // true

Simple Patterns

The following patterns can be used to alter or augment definitions while still keeping api simple and easily readable. Please be aware you provide invalid definition for pattern - InvalidDefinitionError will be thrown with detailed information what type of argument is expected.

any

Matches any value.

match(1, any); // true
match('foo', any); // true
match(new Date(), any); // true
match(true, any); // true
match(false, any); // true
match(undefined, any); // true
match(null, any); // true
match(() => {}, any); // true
match([1, 'foo', true, false], [any]); // true
match({ foo: 'bar' }, { foo: any }); // true
match({ foo: 1 }, { foo: any }); // true

Integer

Matches a signed 32-bit integer. Doesn’t match Infinity, -Infinity, NaN.

match(1, Integer);
match(0, Integer);
match(-1, Integer);
match(-2147483648, Integer);
match(2147483647, Integer);
match(Infinity, Integer); // MatchError: Expected Integer, got Number(null)
match(1.2, Integer); // MatchError: Expected Integer, got Number(1.2)
match('foo', Integer); // MatchError: Expected Integer, got String("foo")

objectIncluding

Matches an Object with the given keys; the value may also have other keys with arbitrary values.

class FooClass {}
match({ foo: 'foo' }, objectIncluding({ foo: String })); // true
match({ foo: new FooClass() }, objectIncluding({ foo: FooClass })); // true
match({ foo: 'foo', bar: 'bar' }, objectIncluding({ foo: String })); // true
match({ foo: 'foo', bar: 'bar' }, objectIncluding({ baz: String })); // MatchError: (Key baz in {"foo":"foo","bar":"bar"}) - Expected undefined to be a String

maybe

Matches: undefined, null or provided pattern. When used on Object it matches only if the key is not set as opposed to the value being set to undefined or null.

match(undefined, maybe(String)); // true
match(null, maybe(String)); // true
match({ required: 'value' }, { required: String, maybe: maybe(String) }); // true
match(
  { required: 'value', maybe: undefined },
  { required: String, maybe: maybe(String) }
); // true
match(
  { required: 'value', maybe: null },
  { required: String, maybe: maybe(String) }
); // true

optional

Matches: undefined or provided pattern. When used in an Object, the behavior is identical to maybe.

match(undefined, optional(String));
match(null, optional(String)); // MatchError: Expected null to be a String
match({ required: 'value' }, { required: String, optional: optional(String) }); // true
match(
  { required: 'value', optional: undefined },
  { required: String, optional: optional(String) }
); // true
match(
  { required: 'value', optional: null },
  { required: String, optional: optional(String) }
); // MatchError: (Key optional in {"required":"value","optional":null}) - Expected null to be a String

oneOf

Matches any value that is a valid match for one of the provided arguments(patterns).

match({ foo: new MyClass() }, objectIncluding({ foo: MyClass })); // true
match('foo', oneOf(String, Number)); // true
match(10, oneOf(String, Number)); // true
match([1, 2, 3], oneOf([String], [Number])); // true
match(['foo', 'bar'], oneOf([String], [Number])); // true
match(1, oneOf(1, 2)); // true
match(new Date('December 17, 1995 03:24:00'), oneOf(String, Date)); // true
match('foo', oneOf('foo', 'bar')); // true
match('qux', oneOf('foo', 'bar')); // MatchError: Expected String("qux") to be one of: String("foo"), String("bar")
match(null, oneOf(null, undefined)); // true
match(undefined, oneOf(null, undefined)); // true
match(true, oneOf(true, false)); // true
match(false, oneOf(true, false)); // true
match(new MyClass(), oneOf(String, MyClass)); // true
match(MyClass, oneOf(String, MyClass)); // true
match('foo', oneOf('foo', 'bar')); // true
match('foo', oneOf('bar', /^foo$/g)); // true
match({ foo: 'bar' }, { foo: oneOf('bar', 'baz') }); // true
match({ foo: 'baz' }, { foo: oneOf('bar', 'baz') }); // true
match({ foo: 'qux' }, { foo: oneOf('bar', 'baz') }); // MatchError: (Key foo in {"foo":"qux"}) - Expected String("qux") to be one of: String("bar"), String("baz")

where

Calls the function with the value as the argument. If returns true, this match is valid. If condition throws a MatchError or returns false, this fails.

match(
  'foo',
  where(value => {
    return value === 'foo';
  })
); // true
match(
  'not-foo',
  where(value => {
    return value === 'foo';
  })
); // Failed WherePattern validation
match(
  `Luke, I'm your father!`,
  where(value => {
    const isValid = value === `Luke, I'm your father!`;
    if (!isValid) {
      throw new MatchError(`nooooooooo!`);
    }
    return isValid;
  })
); // MatchError: nooooooooo!
match(
  { foo: 'foo' },
  {
    foo: where(value => {
      return value === 'foo';
    }),
  }
); // true

Explicit Patterns

instanceOf

Matches if provided value is instance of specific type.

match(null, instanceOf(null)); // true
match(undefined, instanceOf(undefined)); // true
match('foo', instanceOf(String)); // true
match(1, instanceOf(Number)); // true
match(true, instanceOf(Boolean)); // true
match(new Date('December 17, 1995 03:24:00'), instanceOf(Date)); // true
match(/foo/, instanceOf(RegExp)); // true
match(() => {
  return 'foo';
}, instanceOf(Function)); // true
match(['foo'], instanceOf(Array)); // true
match({ foo: 'bar' }, instanceOf(Object)); // true

equals

Matches value content(for Array use ArrayPattern, for Objects use ObjectPattern or just use simple pattern definition(implicit) - otherwise MatchError will be thrown).

match('foo', equals('foo')); // true
match(1, equals(1)); // true
match(true, equals(true)); // true
match(
  new Date('December 17, 1995 03:24:00'),
  equals(new Date('December 17, 1995 03:24:00'))
); // true
match('foo', equals(/foo/)); // true

ArrayPattern

Matches value patterns inside Array.

match(['foo', 1, true], new ArrayPattern(Array)); // true
match(['foo'], new ArrayPattern([String])); // true
match(['foo', 1], new ArrayPattern([String, Number])); // true
match(['foo'], new ArrayPattern(['foo'])); // true
match(['foo', 1], new ArrayPattern(['foo', 1])); // true

ObjectPattern

Matches key:value pairs inside Object.

match({ foo: 'bar' }, new ObjectPattern({ foo: String })); // true
match({ foo: 'bar', baz: 1 }, new ObjectPattern({ foo: String, baz: Number })); // true
match(
  { foo: 'bar', baz: 'qux' },
  new ObjectPattern({ foo: 'bar', baz: 'qux' })
); // true

You can always use instantiation way to define patterns instead of using simple api:

Simple API Instantiation API
any() new AnythingPattern()
equals() new EqualityPattern()
Integer() new IntegerPattern()
objectIncluding() new ObjectIncludingPattern()
oneOf() new OneOfPattern()
maybe() new MaybePattern()
optional() new OptionalPattern()
instanceOf() new TypePattern()
where() new WherePattern()

API

Package supports two ways of validating values:

match

match will return true upon value matching pattern or throw MatchError with human readable error message that can be shown back to the user.

match(value, patternOrDef, isStrict); // true or throws MatchError

import { match } from '@eveble/matchist';
ARGUMENTS
value Any The value to check
patternOrDef Pattern|Any The Pattern instance or implicit definition to match value against
[isStrict=true] Boolean Flag indicating that evaluation should be done in strict mode.

isMatching

isMatching will return boolean on successful or failed matching pattern.

isMatching(value, pattern, isStrict); // true or false

import { isMatching } from '@eveble/matchist';
ARGUMENTS
value Any The value to check
patternOrDef Pattern|Any The Pattern instance or implicit definition to match value against
[isStrict=true] Boolean Flag indicating that evaluation should be done in strict mode.

Testing

Using yarn:

yarn test

License

The MIT License (MIT)

Copyright (c)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.