src/schema.js
import { escape, FieldType, isNumeric } from "./grammar";
/**
* The Schema provides information and utilities for an InfluxDB measurement.
* @private
*/
export class Schema {
constructor(options) {
this.options = options;
this._tagHash = {};
// FieldNames are sorted for performance: when coerceFields is run the
// fields will be added to the output in order.
this._fieldNames = Object.keys(options.fields).sort();
options.tags.forEach((tag) => {
this._tagHash[tag] = true;
});
}
/**
* CoerceFields converts a map of field values to a strings which
* can be injected into the line protocol without further escaping.
* The output is given in [key, value] pairs.
*/
coerceFields(fields) {
let consumed = 0;
const output = [];
this._fieldNames.forEach((field) => {
if (!fields.hasOwnProperty(field)) {
return;
}
const value = fields[field];
const typ = typeof value;
consumed += 1;
if (value === null || value === undefined) {
return;
}
let coerced;
switch (this.options.fields[field]) {
case FieldType.STRING:
coerced = escape.quoted(String(value));
break;
case FieldType.INTEGER:
if (typ !== "number" && !isNumeric(String(value))) {
throw new Error(`Expected numeric value for ${this._ref(field)}, but got '${value}'!`);
}
coerced = String(Math.floor(value)) + "i";
break;
case FieldType.FLOAT:
if (typ !== "number" && !isNumeric(String(value))) {
throw new Error(`Expected numeric value for ${this._ref(field)}, but got '${value}'!`);
}
coerced = String(value);
break;
case FieldType.BOOLEAN:
if (typ !== "boolean") {
throw new Error(`Expected boolean value for ${this._ref(field)}, but got a ${typ}!`);
}
coerced = value ? "T" : "F";
break;
default:
throw new Error(`Unknown field type ${this.options.fields[field]} for ${field} in ` +
`${this._ref()}. Please ensure that your configuration is correct.`);
}
output.push([field, coerced]);
});
const keys = Object.keys(fields);
if (consumed !== keys.length) {
const extraneous = keys.filter((f) => !this._fieldNames.includes(f));
throw new Error("Extraneous fields detected for writing InfluxDB point in " +
`${this._ref()}: \`${extraneous.join("`, `")}\`.`);
}
return output;
}
/**
* Throws an error if the tags include values other than
* what was specified in the schema. It returns a list of tag names.
*/
checkTags(tags) {
const names = Object.keys(tags);
const extraneous = names.filter((tag) => !this._tagHash[tag]);
if (extraneous.length > 0) {
throw new Error("Extraneous tags detected for writing InfluxDB point in " +
`${this._ref()}: \`${extraneous.join("`, `")}\`.`);
}
return names;
}
/**
* Returns the 'db'.'measurement'[.'field'] referencing the current schema.
*/
_ref(field) {
let out = this.options.database + "." + this.options.measurement;
if (field) {
out += "." + field;
}
return out;
}
}
/**
* Coerces the field map to a set of writable values, a la coerceFields,
* using native guesses based on the field datatypes.
* @private
*/
export function coerceBadly(fields) {
return Object.keys(fields)
.sort()
.map((field) => {
const value = fields[field];
if (typeof value === "string") {
return [field, escape.quoted(value)];
}
return [field, String(value)];
});
}