lwm2m
Version:
Library for developing servers and client of OMA Lightweight M2M
285 lines (243 loc) • 6.4 kB
JavaScript
/*
* Copyright 2017 Alexandre Moreno <alex_moreno@tutk.com>
*
* This file is part of node-lwm2m
*
* node-lwm2m is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* node-lwm2m is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with node-lwm2m.
* If not, seehttp://www.gnu.org/licenses/.
*
* For those usages not covered by the GNU Affero General Public License
* please contact with::[contacto@tid.es]
*
*/
;
function _matches(val, type) {
function typeOf(val) {
return Object.prototype.toString.call(val).slice(8, -1);
}
type = type.name || type;
if (Array.isArray(val) && Array.isArray(type)) {
return _matches(val[0], type[0]);
}
switch(type) {
case 'Opaque':
return Buffer.isBuffer(val);
case 'Time':
return 'Date' === typeOf(val);
case 'Integer':
case 'Float':
return 'Number' === typeOf(val);
default:
return type === typeOf(val);
}
}
function _enum(values) {
return function(val) {
return values.indexOf(val) !== -1;
};
}
function _range(values) {
return function(val) {
return values.min <= val && val <= values.max;
};
}
function _validateResource(res, val) {
var ok = _matches(val, res.type);
var check;
if (res.enum) {
check = _enum(res.enum);
} else if (res.range) {
check = _range(res.range);
}
if (ok && check !== undefined) {
return check(val);
}
return ok;
}
function _validateSchema(schema, Schema) {
function _validResourceType(type) {
if (Array.isArray(type)) {
type = type[0];
}
type = type.name || type;
return [
'String',
'Integer',
'Float',
'Boolean',
'Opaque',
'Time',
'Objlnk',
].indexOf(type) > -1;
}
var keys = Object.keys(schema);
for (var i = 0; i < keys.length; ++i) {
var res = schema[keys[i]];
var ok = typeof res === 'object'
&& typeof res.id === 'number'
&& _validResourceType(res.type);
if (res.range && typeof res.range !== 'object') {
ok = false;
}
if (res.enum && !Array.isArray(res.enum)) {
ok = false;
}
if (res.type === 'Objlnk'
&& !(res.schema instanceof Schema)) {
if (typeof res.schema === 'object') {
res.schema = new Schema(res.schema);
} else {
ok = false;
}
}
if (!ok) {
throw new TypeError('Bad definiton `' + keys[i] + '`');
}
}
}
function _ids(schema) {
return Object.keys(schema).reduce(function(acc, key) {
var id = schema[key].id;
acc[id] = key;
return acc;
}, {});
}
/**
* Schema resource type definition
* @typedef {Object} Resource
* @property{('String'|'Integer'|'Float'|'Boolean'|'Opaque'|'Time'|['type'])} type
* @property {number} id Resource ID
* @property {boolean} [required] resource is mandatory. Defaults to `false`
* @property {Array} [enum]
* @property {object} [range]
* @property {number} range.min
* @property {number} range.max
*/
/**
* Schema constructor.
*
* An `Schema` describes the shape of an LwM2M Object and the type of its resources.
* Schemas are used throghout the API for generating/parsing payloads from/to JavaScript values.
*
* See [oma](lib/oma) directory for default definitions.
* See also [thermostat.js](examples/thermostat.js) for an
* example of a composite schema.
*
* **Note**
*
* *LwM2M types will be coerced to JavaScript types and viceversa, e.g. `Time` will return a `Date()`,
* `Opaque` a `Buffer()`, and `Integer`/`Float` a number.*
*
* @example
*
* // IPSO light controller
* var lightControlSchema = new Schema({
* onOff: {
* type: 'Boolean',
* id : 5850,
* required: true
* },
* dimmer: {
* type: 'Integer',
* id: 5851,
* range: { min: 0, max: 100 }
* },
* units: {
* type: 'String',
* id: 5701,
* }
* });
*
* // an object literal matching the schema above
* var lightControl = {
* onOff: true,
* dimmer: 40,
* }
*
* // Bad schema
* var schema = new Schema({
* a: { type: 'String', id: 0 },
* b: { type: 'Error', id: 1 },
* }); // throws TypeError
*
* @param {Object.<string, Resource>} resources
* @throws Will throw an error if fails to validate
*/
function Schema(resources) {
if (!(this instanceof Schema)) {
return new Schema(resources);
}
this.resources = Object.assign({}, resources);
_validateSchema(this.resources, Schema);
this.id = _ids(this.resources);
if (this.constructor === Schema) {
Object.freeze(this.resources);
}
}
/**
* validates `obj` with `schema`.
*
* @param {Object} obj
* @throws Will throw an error if fails to validate
* @example
*
* var schema = new Schema({
* a: { type: String, id: 0 },
* b: { type: Buffer, id: 1 },
* });
*
* schema.validate({
* a: 'foo',
* b: Buffer.from('bar'),
* }); // OK
*
* schema.validate({
* a: 'foo',
* b: 'bar',
* }); // Throws error
*
*/
Schema.prototype.validate = function(obj) {
var keys, ok, key, res, val;
keys = Object.keys(obj);
if (!keys.length) {
throw new TypeError('Invalid object: `' + obj + '`');
}
if (keys.length > 1) { // not single resource
keys = Object.keys(this.resources);
}
for (var i = 0; i < keys.length; ++i) {
key = keys[i];
if (!this.resources[key]) {
throw new TypeError('Invalid resource: `' + key + '`');
}
res = Object.assign({ required: false },
this.resources[key]);
val = obj[key];
ok = _validateResource(res, val);
if (val !== undefined && !ok) {
throw new TypeError('Invalid resource: `' + key + '`');
}
if (res.required && !ok) {
throw new TypeError('Missing resource: `' + key + '`');
}
}
};
Schema.prototype.inspect = function() {
return this.resources;
};
Schema.prototype.setBasename = function(bn) {
return this.basename = bn.replace(/\/?(\d+\/\d+)\/?/, '/$1/');
};
module.exports = Schema;