Home Reference Source

lib6/notification.js

/**
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [https://neo4j.com]
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import * as json from './json';
import { util } from './internal';
import { rawPolyfilledDiagnosticRecord } from './gql-constants';
const unknownGqlStatus = {
    WARNING: {
        gql_status: '01N42',
        status_description: 'warn: unknown warning'
    },
    NO_DATA: {
        gql_status: '02N42',
        status_description: 'note: no data - unknown subcondition'
    },
    INFORMATION: {
        gql_status: '03N42',
        status_description: 'info: unknown notification'
    },
    ERROR: {
        gql_status: '50N42',
        status_description: 'error: general processing exception - unexpected error'
    }
};
/**
 * @typedef {'WARNING' | 'INFORMATION' | 'UNKNOWN'} NotificationSeverityLevel
 */
/**
 * Constants that represents the Severity level in the {@link Notification}
 */
const notificationSeverityLevel = {
    WARNING: 'WARNING',
    INFORMATION: 'INFORMATION',
    UNKNOWN: 'UNKNOWN'
};
Object.freeze(notificationSeverityLevel);
const severityLevels = Object.values(notificationSeverityLevel);
/**
 * @typedef {'HINT' | 'UNRECOGNIZED' | 'UNSUPPORTED' |'PERFORMANCE' | 'TOPOLOGY' | 'SECURITY' | 'DEPRECATION' | 'GENERIC' | 'SCHEMA' | 'UNKNOWN' } NotificationCategory
 */
/**
 * Constants that represents the Category in the {@link Notification}
 */
const notificationCategory = {
    HINT: 'HINT',
    UNRECOGNIZED: 'UNRECOGNIZED',
    UNSUPPORTED: 'UNSUPPORTED',
    PERFORMANCE: 'PERFORMANCE',
    DEPRECATION: 'DEPRECATION',
    TOPOLOGY: 'TOPOLOGY',
    SECURITY: 'SECURITY',
    GENERIC: 'GENERIC',
    SCHEMA: 'SCHEMA',
    UNKNOWN: 'UNKNOWN'
};
Object.freeze(notificationCategory);
const categories = Object.values(notificationCategory);
/**
 * @typedef {NotificationCategory} NotificationClassification
 * @experimental
 */
/**
 * Constants that represents the Classification in the {@link GqlStatusObject}
 * @type {notificationCategory}
 * @experimental
 */
const notificationClassification = notificationCategory;
/**
 * Class for Cypher notifications
 * @access public
 */
class Notification {
    /**
     * Create a Notification instance
     * @constructor
     * @param {Object} notification - Object with notification data
     */
    constructor(notification) {
        /**
         * The code
         * @type {string}
         * @public
         */
        this.code = notification.code;
        /**
         * The title
         * @type {string}
         * @public
         */
        this.title = notification.title;
        /**
         * The description
         * @type {string}
         * @public
         */
        this.description = notification.description;
        /**
         * The raw severity
         *
         * Use {@link Notification#rawSeverityLevel} for the raw value or {@link Notification#severityLevel} for an enumerated value.
         *
         * @type {string}
         * @public
         * @deprecated This property will be removed in 6.0.
         */
        this.severity = notification.severity;
        /**
         * The position which the notification had occur.
         *
         * @type {NotificationPosition}
         * @public
         */
        this.position = _constructPosition(notification.position);
        /**
         * The severity level
         *
         * @type {NotificationSeverityLevel}
         * @public
         * @example
         * const { summary } = await session.run("RETURN 1")
         *
         * for (const notification of summary.notifications) {
         *     switch(notification.severityLevel) {
         *         case neo4j.notificationSeverityLevel.INFORMATION: // or simply 'INFORMATION'
         *             console.info(`${notification.title} - ${notification.description}`)
         *             break
         *         case neo4j.notificationSeverityLevel.WARNING: // or simply 'WARNING'
         *             console.warn(`${notification.title} - ${notification.description}`)
         *             break
         *         case neo4j.notificationSeverityLevel.UNKNOWN: // or simply 'UNKNOWN'
         *         default:
         *             // the raw info came from the server could be found at notification.rawSeverityLevel
         *             console.log(`${notification.title} - ${notification.description}`)
         *             break
         *     }
         * }
         */
        this.severityLevel = _asEnumerableSeverity(notification.severity);
        /**
         * The severity level returned by the server without any validation.
         *
         * @type {string}
         * @public
         */
        this.rawSeverityLevel = notification.severity;
        /**
         * The category
         *
         * @type {NotificationCategory}
         * @public
         * @example
         * const { summary } = await session.run("RETURN 1")
         *
         * for (const notification of summary.notifications) {
         *     switch(notification.category) {
         *         case neo4j.notificationCategory.QUERY: // or simply 'QUERY'
         *             console.info(`${notification.title} - ${notification.description}`)
         *             break
         *         case neo4j.notificationCategory.PERFORMANCE: // or simply 'PERFORMANCE'
         *             console.warn(`${notification.title} - ${notification.description}`)
         *             break
         *         case neo4j.notificationCategory.UNKNOWN: // or simply 'UNKNOWN'
         *         default:
         *             // the raw info came from the server could be found at notification.rawCategory
         *             console.log(`${notification.title} - ${notification.description}`)
         *             break
         *     }
         * }
         */
        this.category = _asEnumerableClassification(notification.category);
        /**
         * The category returned by the server without any validation.
         *
         * @type {string|undefined}
         * @public
         */
        this.rawCategory = notification.category;
    }
}
/**
 * Representation for GqlStatusObject found when executing a query.
 * <p>
 * This object represents a status of query execution.
 * This status is a superset of {@link Notification}.
 *
 * @experimental
 * @public
 */
class GqlStatusObject {
    /**
     *
     * @param rawGqlStatusObject
     * @private
     */
    constructor(rawGqlStatusObject) {
        var _a;
        /**
         * The GQLSTATUS
         *
         * @type {string}
         * @public
         */
        this.gqlStatus = rawGqlStatusObject.gql_status;
        /**
         * The GQLSTATUS description
         *
         * @type {string}
         * @public
         */
        this.statusDescription = rawGqlStatusObject.status_description;
        /**
         * The diagnostic record as it is.
         *
         * @type {object}
         * @public
         */
        this.diagnosticRecord = (_a = rawGqlStatusObject.diagnostic_record) !== null && _a !== void 0 ? _a : {};
        /**
         * The position at which the notification had occurred.
         *
         * @type {NotificationPosition | undefined}
         * @public
         */
        this.position = this.diagnosticRecord._position != null ? _constructPosition(this.diagnosticRecord._position) : undefined;
        /**
         * The severity
         *
         * @type {NotificationSeverityLevel}
         * @public
         * @example
         * const { summary } = await session.run("RETURN 1")
         *
         * for (const gqlStatusObject of summary.gqlStatusObjects) {
         *     switch(gqlStatusObject.severity) {
         *         case neo4j.notificationSeverityLevel.INFORMATION: // or simply 'INFORMATION'
         *             console.info(gqlStatusObject.statusDescription)
         *             break
         *         case neo4j.notificationSeverityLevel.WARNING: // or simply 'WARNING'
         *             console.warn(gqlStatusObject.statusDescription)
         *             break
         *         case neo4j.notificationSeverityLevel.UNKNOWN: // or simply 'UNKNOWN'
         *         default:
         *             // the raw info came from the server could be found at gqlStatusObject.rawSeverity
         *             console.log(gqlStatusObject.statusDescription)
         *             break
         *     }
         * }
         */
        this.severity = _asEnumerableSeverity(this.diagnosticRecord._severity);
        /**
         * The severity returned in the diagnostic record from the server without any validation.
         *
         * @type {string | undefined}
         * @public
         */
        this.rawSeverity = this.diagnosticRecord._severity;
        /**
         * The classification
         *
         * @type {NotificationClassification}
         * @public
         * @example
         * const { summary } = await session.run("RETURN 1")
         *
         * for (const gqlStatusObject of summary.gqlStatusObjects) {
         *     switch(gqlStatusObject.classification) {
         *         case neo4j.notificationClassification.QUERY: // or simply 'QUERY'
         *             console.info(gqlStatusObject.statusDescription)
         *             break
         *         case neo4j.notificationClassification.PERFORMANCE: // or simply 'PERFORMANCE'
         *             console.warn(gqlStatusObject.statusDescription)
         *             break
         *         case neo4j.notificationClassification.UNKNOWN: // or simply 'UNKNOWN'
         *         default:
         *             // the raw info came from the server can be found at notification.rawCategory
         *             console.log(gqlStatusObject.statusDescription)
         *             break
         *     }
         * }
         */
        this.classification = _asEnumerableClassification(this.diagnosticRecord._classification);
        /**
         * The category returned by the server without any validation.
         *
         * @type {string|undefined}
         * @public
         */
        this.rawClassification = this.diagnosticRecord._classification;
        /**
         * Indicates if this object represents a notification and it can be filtered using
         * NotificationFilter.
         *
         * Only GqlStatusObject which is Notification has meaningful position, severity and
         * classification.
         *
         * @type {boolean}
         * @public
         */
        this.isNotification = rawGqlStatusObject.neo4j_code != null;
        Object.freeze(this);
    }
    /**
     * The json string representation of the diagnostic record.
     * The goal of this method is provide a serialized object for human inspection.
     *
     * @type {string}
     * @public
     */
    get diagnosticRecordAsJsonString() {
        return json.stringify(this.diagnosticRecord, { useCustomToString: true });
    }
}
/**
 *
 * @private
 * @param status
 * @returns {Notification|undefined}
 */
function polyfillNotification(status) {
    var _a, _b, _c;
    // Non notification status should have neo4j_code
    if (status.neo4j_code == null) {
        return undefined;
    }
    return new Notification({
        code: status.neo4j_code,
        title: status.title,
        description: status.description,
        severity: (_a = status.diagnostic_record) === null || _a === void 0 ? void 0 : _a._severity,
        category: (_b = status.diagnostic_record) === null || _b === void 0 ? void 0 : _b._classification,
        position: (_c = status.diagnostic_record) === null || _c === void 0 ? void 0 : _c._position
    });
}
/**
 * @private
 * @param notification
 * @returns {GqlStatusObject}
 */
function polyfillGqlStatusObject(notification) {
    var _a;
    const defaultStatus = notification.severity === notificationSeverityLevel.WARNING ? unknownGqlStatus.WARNING : unknownGqlStatus.INFORMATION;
    const polyfilledRawObj = {
        gql_status: defaultStatus.gql_status,
        status_description: (_a = notification.description) !== null && _a !== void 0 ? _a : defaultStatus.status_description,
        neo4j_code: notification.code,
        title: notification.title,
        diagnostic_record: Object.assign({}, rawPolyfilledDiagnosticRecord)
    };
    if (notification.severity != null) {
        polyfilledRawObj.diagnostic_record._severity = notification.severity;
    }
    if (notification.category != null) {
        polyfilledRawObj.diagnostic_record._classification = notification.category;
    }
    if (notification.position != null) {
        polyfilledRawObj.diagnostic_record._position = notification.position;
    }
    return new GqlStatusObject(polyfilledRawObj);
}
/**
 * This objects are used for polyfilling the first status on the status list
 *
 * @private
 */
const staticGqlStatusObjects = {
    SUCCESS: new GqlStatusObject({
        gql_status: '00000',
        status_description: 'note: successful completion',
        diagnostic_record: rawPolyfilledDiagnosticRecord
    }),
    NO_DATA: new GqlStatusObject({
        gql_status: '02000',
        status_description: 'note: no data',
        diagnostic_record: rawPolyfilledDiagnosticRecord
    }),
    NO_DATA_UNKNOWN_SUBCONDITION: new GqlStatusObject(Object.assign(Object.assign({}, unknownGqlStatus.NO_DATA), { diagnostic_record: rawPolyfilledDiagnosticRecord })),
    OMITTED_RESULT: new GqlStatusObject({
        gql_status: '00001',
        status_description: 'note: successful completion - omitted result',
        diagnostic_record: rawPolyfilledDiagnosticRecord
    })
};
Object.freeze(staticGqlStatusObjects);
/**
 *
 * @private
 * @param metadata
 * @returns
 */
function buildGqlStatusObjectFromMetadata(metadata) {
    var _a, _b;
    function getGqlStatusObjectFromStreamSummary(summary) {
        if ((summary === null || summary === void 0 ? void 0 : summary.have_records_streamed) === true) {
            return staticGqlStatusObjects.SUCCESS;
        }
        if ((summary === null || summary === void 0 ? void 0 : summary.has_keys) === false) {
            return staticGqlStatusObjects.OMITTED_RESULT;
        }
        if ((summary === null || summary === void 0 ? void 0 : summary.pulled) === true) {
            return staticGqlStatusObjects.NO_DATA;
        }
        return staticGqlStatusObjects.NO_DATA_UNKNOWN_SUBCONDITION;
    }
    if (metadata.statuses != null) {
        return metadata.statuses.map((status) => new GqlStatusObject(status));
    }
    const clientGenerated = getGqlStatusObjectFromStreamSummary(metadata.stream_summary);
    const polyfilledObjects = [clientGenerated, ...((_b = (_a = metadata.notifications) === null || _a === void 0 ? void 0 : _a.map(polyfillGqlStatusObject)) !== null && _b !== void 0 ? _b : [])];
    return polyfilledObjects.sort((a, b) => calculateWeight(a) - calculateWeight(b));
}
const gqlStatusWeightByClass = Object.freeze({
    '02': 0,
    '01': 1,
    '00': 2
});
/**
 * GqlStatus weight
 *
 * @private
 */
function calculateWeight(gqlStatusObject) {
    var _a, _b;
    const gqlClass = (_a = gqlStatusObject.gqlStatus) === null || _a === void 0 ? void 0 : _a.slice(0, 2);
    // @ts-expect-error
    return (_b = gqlStatusWeightByClass[gqlClass]) !== null && _b !== void 0 ? _b : 9999;
}
/**
 *
 * @private
 * @param metadata
 * @returns
 */
function buildNotificationsFromMetadata(metadata) {
    if (metadata.notifications != null) {
        return metadata.notifications.map((n) => new Notification(n));
    }
    if (metadata.statuses != null) {
        return metadata.statuses.map(polyfillNotification).filter((n) => n != null);
    }
    return [];
}
/**
 *
 * @private
 * @param pos
 * @returns {NotificationPosition}
 */
function _constructPosition(pos) {
    if (pos == null) {
        return {};
    }
    /* eslint-disable @typescript-eslint/no-non-null-assertion */
    return {
        offset: util.toNumber(pos.offset),
        line: util.toNumber(pos.line),
        column: util.toNumber(pos.column)
    };
    /* eslint-enable @typescript-eslint/no-non-null-assertion */
}
function _asEnumerableSeverity(severity) {
    return severityLevels.includes(severity)
        ? severity
        : notificationSeverityLevel.UNKNOWN;
}
function _asEnumerableClassification(classification) {
    return categories.includes(classification)
        ? classification
        : notificationClassification.UNKNOWN;
}
export default Notification;
export { notificationSeverityLevel, notificationCategory, notificationClassification, Notification, GqlStatusObject, polyfillGqlStatusObject, polyfillNotification, buildGqlStatusObjectFromMetadata, buildNotificationsFromMetadata };