Skip to content

Commit

Permalink
Unit tests, docs, etc
Browse files Browse the repository at this point in the history
  • Loading branch information
RangerMauve committed Apr 21, 2017
1 parent 1bd3e26 commit 0dad49e
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 1 deletion.
30 changes: 30 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"extends": [
"eslint:recommended",
"plugin:node/recommended"
],
"env": {},
"globals": {
"window": true
},
"plugins": [
"node"
],
"rules": {
"no-undef": 2,
"no-unused-vars": 2,
"strict": [2, "global"],
"new-cap": 0,
"indent": ["error", "tab", {"MemberExpression": 1, "ArrayExpression": 1, "ObjectExpression": 1 }],
"no-console": 0,
"no-invalid-this": 0,
"node/no-missing-require": 1,
"no-trailing-spaces": 2,
"semi": ["error", "always"],
"key-spacing": ["warn", { "beforeColon": false, "afterColon": true }],
"space-infix-ops": ["warn"],
"object-curly-newline": ["warn", {"minProperties": 1}],
"no-shadow": ["error"],
"no-undef-init": ["error"]
}
}
71 changes: 70 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,71 @@
# mqtt-pattern
Fast library for matching MQTT patterns with named wildcards
Fast library for matching MQTT patterns with named wildcards to extract data from topics

Successor to [mqtt-regex](./mqtt-regex)

## Example:

``` javascript
var MQTTPattern = require("mqtt-pattern");

// Wildcards in patterns don't need names
var pattern = "device/+id/+/#data";

var topic = "device/fitbit/heartrate/rate/bpm";

var params = MQTTPattern.exec(pattern, topic);

// params will be
{
id: "fitbit",
data: ["rate", "bmp"]
}

```

## Installing

With NPM:

```bash
npm install --save mqtt-pattern
```

## API

### `exec(pattern : String, topic : String) : Object | null`
Validates that `topic` fits the `pattern` and parses out any parameters.
If the topic doesn't match, it returns `null`

### `matches(pattern : String, topic : String) : Boolean`
Validates whether `topic` fits the `pattern`. Ignores parameters.

### `extract(pattern : String, topic : String) : Object`
Traverses the `pattern` and attempts to fetch parameters from the `topic`.
Useful if you know in advance that your `topic` will be valid and want to extract data.
If the `topic` doesn't match, or the `pattern` doesn't contain named wildcards, returns an empty object.
Do not use this for validation.

## How params work

MQTT defines two types of "wildcards", one for matching a single section of the path (`+`), and one for zero or more sections of the path (`#`).
Note that the `#` wildcard must only be used if it's at the end of the topic.
This library was inspired by the syntax in the routers for web frameworks.

### Examples of topic patterns:

#### user/+id/#path
This would match paths that start with `user/`, and then extract the next section as the user `id`.
Then it would get the following paths and turn them into an array for the `path` param.
Here is some input/output that you can expect:

user/bob/status/mood: {id: "bob", path:["status","mood"]
user/bob: {id:"bob", path: []}
user/bob/ishungry: {id: "bob", path: ["ishungry"]

#### device/+/+/component/+type/#path
Not all wildcards need to be associated with a parameter, and it could be useful to use plain MQTT topics.
In this example you might only care about the status of some part of a device, and are willing to ignore a part of the path.
Here are some examples of what this might be used with:

device/deviceversion/deviceidhere/component/infrared/status/active: {type:"infrared",path: ["status","active"]}
33 changes: 33 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "mqtt-pattern",
"version": "1.0.0",
"description": "Fast library for matching MQTT patterns with named wildcards",
"main": "index.js",
"scripts": {
"test": "node test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/RangerMauve/mqtt-pattern.git"
},
"keywords": [
"mqtt",
"pattern",
"match",
"topic"
],
"author": "rangermauve",
"license": "MIT",
"bugs": {
"url": "https://github.com/RangerMauve/mqtt-pattern/issues"
},
"homepage": "https://github.com/RangerMauve/mqtt-pattern#readme",
"devDependencies": {
"eslint": "^3.19.0",
"eslint-plugin-node": "^4.2.2",
"tape": "^4.6.3"
},
"dependencies": {
"mqtt-match": "^1.0.2"
}
}
101 changes: 101 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use strict";
var test = require("tape");

var MQTTPattern = require("./");

test("matches() supports patterns with no wildcards", function (t) {
t.plan(1);
t.ok(MQTTPattern.matches("foo/bar/baz", "foo/bar/baz"), "Matched topic");
});

test("matches() doesn't match different topics", function (t) {
t.plan(1);
t.notOk(MQTTPattern.matches("foo/bar/baz", "baz/bar/foo"), "Didn't match topic");
});

test("matches() supports patterns with # at the beginning", function (t) {
t.plan(1);
t.ok(MQTTPattern.matches("#", "foo/bar/baz"), "Matched topic");
});

test("matches() supports patterns with # at the end", function (t) {
t.plan(1);
t.ok(MQTTPattern.matches("foo/#", "foo/bar/baz"), "Matched topic");
});

test("matches() doesn't support # wildcards with more after them", function (t) {
t.plan(1);
t.notOk(MQTTPattern.matches("#/bar/baz", "foo/bar/baz"), "Didn't match topic");
});

test("matches() supports patterns with + at the beginning", function (t) {
t.plan(1);
t.ok(MQTTPattern.matches("+/bar/baz", "foo/bar/baz"), "Matched topic");
});

test("matches() supports patterns with + at the end", function (t) {
t.plan(1);
t.ok(MQTTPattern.matches("foo/bar/+", "foo/bar/baz"), "Matched topic");
});

test("matches() supports patterns with + in the middle", function (t) {
t.plan(1);
t.ok(MQTTPattern.matches("foo/+/baz", "foo/bar/baz"), "Matched topic");
});

test("matches() supports patterns multiple wildcards", function (t) {
t.plan(1);
t.ok(MQTTPattern.matches("foo/+/#", "foo/bar/baz"), "Matched topic");
});

test("matches() supports named wildcards", function (t) {
t.plan(1);
t.ok(MQTTPattern.matches("foo/+something/#else", "foo/bar/baz"), "Matched topic");
});

test("extract() returns empty object of there's nothing to extract", function (t) {
t.plan(1);
t.deepEqual(MQTTPattern.extract("foo/bar/baz", "foo/bar/baz"), {}, "Extracted empty object");
});

test("extract() returns empty object if wildcards don't have label", function (t) {
t.plan(1);
t.deepEqual(MQTTPattern.extract("foo/+/#", "foo/bar/baz"), {}, "Extracted empty object");
});

test("extract() returns object with an array for # wildcard", function (t) {
t.plan(1);
t.deepEqual(MQTTPattern.extract("foo/#something", "foo/bar/baz"), {
something: ["bar", "baz"]
}, "Extracted param");
});

test("extract() returns object with a string for + wildcard", function (t) {
t.plan(1);
t.deepEqual(MQTTPattern.extract("foo/+hello/+world", "foo/bar/baz"), {
hello: "bar",
world: "baz"
}, "Extracted params");
});

test("extract() parses params from all wildcards", function (t) {
t.plan(1);
t.deepEqual(MQTTPattern.extract("+hello/+world/#wow", "foo/bar/baz/fizz"), {
hello: "foo",
world: "bar",
wow: ["baz", "fizz"]
}, "Extracted params");
});

test("exec() returns null if it doesn't match", function (t) {
t.plan(1);
t.equal(MQTTPattern.exec("hello/world", "foo/bar/baz"), null, "Got null");
});

test("exec() returns params if they can be parsed", function(t){
t.plan(1);
t.deepEqual(MQTTPattern.exec("foo/+hello/#world", "foo/bar/baz"), {
hello: "bar",
world: ["baz"]
}, "Extracted params");
});

0 comments on commit 0dad49e

Please sign in to comment.