mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-11 23:04:52 +09:00
Merge pull request #942 from Cooldude2606/refactor/sub-filters-again
Make SubscriberFilter extend Set and expose errors in EventSubscriber
This commit is contained in:
2 files changed
+60
-26
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { Type, Static } from "@sinclair/typebox";
|
||||
import { Link, Event, EventClass, RequestHandler, WebSocketBaseConnector } from "./link";
|
||||
import { Address, MessageRequest, IUser, JsonBoolean, StringEnum } from "./data";
|
||||
import { Address, MessageRequest, IUser, JsonBoolean, StringEnum, AccountDetails } from "./data";
|
||||
import isDeepStrictEqual from "./is_deep_strict_equal";
|
||||
import { RequestError } from "./errors";
|
||||
import { logger } from "./logging";
|
||||
@@ -14,25 +14,27 @@ export type EventSubscriberCallback<T> = (event: T | null, synced: boolean) => v
|
||||
* When isEmpty() is true, no strings match this filter.
|
||||
* Otherwise, literal string matching is performed against a set.
|
||||
*/
|
||||
export class SubscriptionFilters {
|
||||
export class SubscriptionFilters extends Set<string> {
|
||||
private constructor(
|
||||
private _all: boolean,
|
||||
private _filters: Set<string>,
|
||||
) {}
|
||||
_filters: Iterable<string> = [],
|
||||
) {
|
||||
super(_filters);
|
||||
}
|
||||
|
||||
static jsonSchema = Type.Array(Type.String());
|
||||
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||
return new this(false, new Set(json));
|
||||
return new this(false, json);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return [...this._filters];
|
||||
return [...this];
|
||||
}
|
||||
|
||||
/** Creates a subscription filter accepting all strings */
|
||||
static all() {
|
||||
return new this(true, new Set());
|
||||
return new this(true);
|
||||
}
|
||||
|
||||
isAll() {
|
||||
@@ -41,11 +43,11 @@ export class SubscriptionFilters {
|
||||
|
||||
/** Creates a subscription filter rejecting all strings */
|
||||
static empty() {
|
||||
return new this(false, new Set());
|
||||
return new this(false);
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
return !this._all && this._filters.size === 0;
|
||||
return !this._all && this.size === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,20 +69,20 @@ export class SubscriptionFilters {
|
||||
if (this._all) {
|
||||
return "[SubscriptionFilters All]";
|
||||
}
|
||||
if (this._filters.size === 0) {
|
||||
if (this.size === 0) {
|
||||
return "[SubscriptionFilters Empty]";
|
||||
}
|
||||
return `[SubscriptionFilters Set<${this._filters.size}>]`;
|
||||
return `[SubscriptionFilters Set<${this.size}>]`;
|
||||
}
|
||||
|
||||
/** Returns true if this filter accepts the given string */
|
||||
accepts(value: string) {
|
||||
return this._all || this._filters.has(value);
|
||||
return this._all || this.has(value);
|
||||
}
|
||||
|
||||
/** Returns true if this filter extends the other */
|
||||
extends(other: this) {
|
||||
return this._all || (!other._all && [...other._filters.values()].every(value => this._filters.has(value)));
|
||||
return this._all || (!other._all && [...other.values()].every(value => this.has(value)));
|
||||
}
|
||||
|
||||
/** Returns true if there is overlap between two filters */
|
||||
@@ -91,8 +93,8 @@ export class SubscriptionFilters {
|
||||
if (other._all) {
|
||||
return !this.isEmpty();
|
||||
}
|
||||
for (const filter of other._filters) {
|
||||
if (this._filters.has(filter)) {
|
||||
for (const filter of other) {
|
||||
if (this.has(filter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -106,11 +108,11 @@ export class SubscriptionFilters {
|
||||
}
|
||||
if (other._all) {
|
||||
this._all = true;
|
||||
this._filters.clear();
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
for (const filter of other._filters) {
|
||||
this._filters.add(filter);
|
||||
for (const filter of other) {
|
||||
this.add(filter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,14 +124,14 @@ export class SubscriptionFilters {
|
||||
subtract(other: this) {
|
||||
if (other._all) {
|
||||
this._all = false;
|
||||
this._filters.clear();
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
if (this._all) {
|
||||
return; // Cannot subtract from all
|
||||
}
|
||||
for (const filter of other._filters) {
|
||||
this._filters.delete(filter);
|
||||
for (const filter of other) {
|
||||
this.delete(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,8 +408,10 @@ export class EventSubscriber<E, S = null> {
|
||||
lastUpdatedMs = -1;
|
||||
/** True if this subscriber is currently synced with the source */
|
||||
synced = false;
|
||||
/** Error value from last attempted subscription event */
|
||||
error = null as Error | null;
|
||||
/** Repeat calls to getSnapshot will return the same readonly copy unless values has updated */
|
||||
private _snapshot: readonly [S, boolean] = [this.makeSnapshot(), false];
|
||||
private _snapshot: readonly [S, boolean, Error | null] = [this.makeSnapshot(), false, null];
|
||||
private _snapshotLastUpdatedMs = -1;
|
||||
/** Callbacks will be called when an event is received or the synced state changes */
|
||||
private _callbacks = new Array<EventSubscriberCallback<E>>();
|
||||
@@ -555,10 +559,14 @@ export class EventSubscriber<E, S = null> {
|
||||
* Obtain a snapshot of the current state of the tracked resource
|
||||
* @returns tuple of values map snapshot and synced property.
|
||||
*/
|
||||
getSnapshot(): readonly [S, boolean] {
|
||||
if (this._snapshotLastUpdatedMs !== this.lastUpdatedMs || this._snapshot[1] !== this.synced) {
|
||||
getSnapshot(): readonly [S, boolean, Error | null] {
|
||||
if (
|
||||
this._snapshotLastUpdatedMs !== this.lastUpdatedMs
|
||||
|| this._snapshot[1] !== this.synced
|
||||
|| this._snapshot[2] !== this.error
|
||||
) {
|
||||
this._snapshotLastUpdatedMs = this.lastUpdatedMs;
|
||||
this._snapshot = [this.makeSnapshot(), this.synced];
|
||||
this._snapshot = [this.makeSnapshot(), this.synced, this.error];
|
||||
}
|
||||
return this._snapshot;
|
||||
}
|
||||
@@ -581,6 +589,7 @@ export class EventSubscriber<E, S = null> {
|
||||
const updatesSent = await this.control.sendTo("controller", new SubscriptionRequest(
|
||||
entry.name, action, this.lastUpdatedMs, filters
|
||||
));
|
||||
this.error = null;
|
||||
if (!updatesSent) {
|
||||
this.synced = this._hasSubscriptions();
|
||||
this._notify(null);
|
||||
@@ -589,6 +598,9 @@ export class EventSubscriber<E, S = null> {
|
||||
if (!(err instanceof RequestError) || err.code !== "SessionLost") {
|
||||
logger.error(`Unexpected error updating ${entry.name} subscription:\n${err.stack}`);
|
||||
}
|
||||
if (err instanceof Error) {
|
||||
this.error = err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,7 +792,10 @@ describe("lib/subscriptions", function() {
|
||||
});
|
||||
|
||||
describe("class EventSubscriber", function() {
|
||||
let mockControl, eventSubscriber;
|
||||
/** @type {MockControl} */
|
||||
let mockControl;
|
||||
/** @type {lib.EventSubscriber} */
|
||||
let eventSubscriber;
|
||||
|
||||
beforeEach(function() {
|
||||
mockControl = new MockControl(new MockConnector(
|
||||
@@ -1120,6 +1123,25 @@ describe("lib/subscriptions", function() {
|
||||
}
|
||||
assert.equal(logged, undefined);
|
||||
});
|
||||
|
||||
it("should expose errors through the snapshot", async function() {
|
||||
eventSubscriber.control.sendTo = function() {
|
||||
throw new lib.PermissionError("Permission denied");
|
||||
};
|
||||
|
||||
const unsubscribe = eventSubscriber.subscribe(() => {});
|
||||
await new Promise(r => setImmediate(r));
|
||||
const [,, error1] = eventSubscriber.getSnapshot();
|
||||
assert.ok(error1 instanceof lib.PermissionError);
|
||||
assert.equal(error1.message, "Permission denied");
|
||||
assert.equal(error1.code, "PermissionError");
|
||||
|
||||
eventSubscriber.control.sendTo = function() {};
|
||||
unsubscribe();
|
||||
await new Promise(r => setImmediate(r));
|
||||
const [,, error2] = eventSubscriber.getSnapshot();
|
||||
assert.deepEqual(error2, null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user