dbus-sdk
Version:
A Node.js SDK for interacting with DBus, enabling seamless service calling and exposure with TypeScript support
174 lines (173 loc) • 8.52 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Signature = void 0;
const Errors_1 = require("./Errors");
/**
* A mapping of opening brackets to their corresponding closing brackets.
* Used to validate and parse nested structures like dictionaries and structs in DBus signatures.
*/
const match = {
'{': '}',
'(': ')'
};
/**
* A mapping of known DBus signature type characters to a boolean flag.
* Used to validate whether a character represents a valid DBus type during signature parsing.
*/
const knownTypes = {};
'(){}ybnqiuxtdsogarvehm*?@&^'.split('').forEach(function (c) {
knownTypes[c] = true;
});
/**
* Class for handling DBus signature parsing and compatibility checking.
* Provides static methods to parse signature strings into data type trees and compare signatures for compatibility.
*/
class Signature {
/**
* Parses a DBus signature string into an array of data type structures.
* Converts a signature string (e.g., 'a{sv}') into a tree-like structure of DataType objects.
* Throws a SignatureError if the signature is invalid or contains unknown types.
*
* @param signature - The DBus signature string to parse (e.g., 'a{sv}' for an array of dictionary entries).
* @returns An array of DataType objects representing the parsed signature structure.
* @throws {SignatureError} If the signature is invalid or contains unrecognized types.
*/
static parseSignature(signature) {
let index = 0;
/**
* Advances the index and returns the next character from the signature string as a Types enum value.
* Returns null if the end of the string is reached.
*
* @returns The next character as a Types value, or null if at the end of the signature.
*/
function next() {
if (index < signature.length) {
const c = signature[index];
++index;
return c;
}
return null;
}
/**
* Parses a single type character into a DataType object, handling nested structures recursively.
* Validates the type and processes nested children for arrays, structs, and dictionary entries.
*
* @param c - The current type character (as a Types enum value) to parse.
* @returns A DataType object representing the parsed type and its children (if any).
* @throws {SignatureError} If the signature is malformed or ends unexpectedly.
*/
function parseOne(c) {
const parsingType = c;
/**
* Checks if the current character exists (i.e., not null) and throws an error if the signature ends unexpectedly.
*
* @param c - The character to check.
* @returns The same character if it exists.
* @throws {SignatureError} If the character is null (unexpected end of signature).
*/
function checkNotEnd(c) {
if (!c)
throw new Errors_1.SignatureError(`Bad signature: unexpected end (${parsingType})`);
return c;
}
// Validate that the type character is a known DBus type
if (!knownTypes[c])
throw new Errors_1.SignatureError(`Unknown type: "${c}" in signature "${signature}"`);
let ele;
const res = { type: c, child: [] };
switch (c) {
case 'a': // Array type - must be followed by the element type
ele = next();
checkNotEnd(ele);
res.child.push(parseOne(ele));
return res;
case '{': // Dictionary entry - key-value pair, must be closed with '}'
case '(': // Struct - ordered list of types, must be closed with ')'
while ((ele = next()) !== null && ele !== match[c])
res.child.push(parseOne(ele));
checkNotEnd(ele);
return res;
}
// For basic types (e.g., 's', 'i'), no children are added
return res;
}
// Parse the entire signature string into an array of top-level DataType objects
const ret = [];
let c;
while ((c = next()) !== null)
ret.push(parseOne(c));
return ret;
}
/**
* Compare two signature strings to check if they are compatible.
* The base signature is the reference (e.g., method definition), and the input signature is the one to compare (e.g., method call).
* If the base signature contains 'v' (variant), any type in the input signature at that position is considered compatible.
* This rule applies recursively to nested structures.
* Returns false instead of throwing an error if signatures are incompatible.
* Handles cases where signatures are undefined or empty.
*
* @param baseSignature - The base signature string (reference, e.g., method definition "vs"). Can be undefined.
* @param inputSignature - The input signature string to compare (e.g., method call "a{sv}s"). Can be undefined.
* @returns True if the signatures are compatible, false if they are not or if inputs are invalid.
* @throws {SignatureError} If either signature is invalid (non-empty and cannot be parsed).
*/
static areSignaturesCompatible(baseSignature, inputSignature) {
// Normalize undefined signatures to empty strings for consistent handling
baseSignature = baseSignature ? baseSignature : '';
inputSignature = inputSignature ? inputSignature : '';
// Step 1: Handle undefined cases
if (baseSignature === undefined && inputSignature === undefined) {
return true; // Both undefined, consider compatible (no signature to compare)
}
if (baseSignature === undefined || inputSignature === undefined) {
return false; // One is undefined while the other is not, consider incompatible
}
// Step 2: Parse both signatures into DataType arrays
// Empty strings are handled by parseSignature and result in empty arrays
const baseDataTypes = Signature.parseSignature(baseSignature);
const inputDataTypes = Signature.parseSignature(inputSignature);
// Step 3: Compare the parsed structures for top-level length
if (baseDataTypes.length !== inputDataTypes.length) {
return false; // Different number of top-level types means incompatibility
}
/**
* Recursively compares two DataType objects to check if they are compatible.
* Handles special case for 'v' (variant) in base signature, which accepts any type.
*
* @param baseDt - The base DataType to compare against.
* @param inputDt - The input DataType to check for compatibility.
* @returns True if the types are compatible, false otherwise.
*/
const compareDataTypes = (baseDt, inputDt) => {
// If base type is 'v' (variant), accept any input type as compatible at this position
if (baseDt.type === 'v') {
return true; // Variant accepts any type
}
// Otherwise, check if types match
if (baseDt.type !== inputDt.type) {
return false; // Types differ, not compatible
}
// If the type has children (like array, struct, dict), compare them recursively
if (baseDt.child && inputDt.child) {
if (baseDt.child.length !== inputDt.child.length) {
return false; // Different number of child types, not compatible
}
for (let i = 0; i < baseDt.child.length; i++) {
if (!compareDataTypes(baseDt.child[i], inputDt.child[i])) {
return false; // A child type is incompatible
}
}
}
return true; // All checks passed for this type
};
// Step 5: Compare all top-level types
for (let i = 0; i < baseDataTypes.length; i++) {
if (!compareDataTypes(baseDataTypes[i], inputDataTypes[i])) {
return false; // A top-level type is incompatible
}
}
// All checks passed, signatures are compatible
return true;
}
}
exports.Signature = Signature;