mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Support async default values in getDefault
This commit is contained in:
parent
c73ef50e48
commit
a1e916b73a
@ -15,6 +15,7 @@ import { InternalServerError } from '../util/errors/InternalServerError';
|
|||||||
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
|
||||||
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
||||||
import { IdentifierMap } from '../util/map/IdentifierMap';
|
import { IdentifierMap } from '../util/map/IdentifierMap';
|
||||||
|
import { getDefault } from '../util/map/MapUtil';
|
||||||
import { readableToQuads } from '../util/StreamUtil';
|
import { readableToQuads } from '../util/StreamUtil';
|
||||||
import { ACL } from '../util/Vocabularies';
|
import { ACL } from '../util/Vocabularies';
|
||||||
import { getAccessControlledResources } from './AcpUtil';
|
import { getAccessControlledResources } from './AcpUtil';
|
||||||
@ -78,12 +79,8 @@ export class AcpReader extends PermissionReader {
|
|||||||
// Extract all the policies relevant for the target
|
// Extract all the policies relevant for the target
|
||||||
const identifiers = this.getAncestorIdentifiers(target);
|
const identifiers = this.getAncestorIdentifiers(target);
|
||||||
for (const identifier of identifiers) {
|
for (const identifier of identifiers) {
|
||||||
let acrs = resourceCache.get(identifier);
|
const acrs = await getDefault(resourceCache, identifier, async(): Promise<IAccessControlledResource[]> =>
|
||||||
if (!acrs) {
|
[ ...getAccessControlledResources(await this.readAcrData(identifier)) ]);
|
||||||
const data = await this.readAcrData(identifier);
|
|
||||||
acrs = [ ...getAccessControlledResources(data) ];
|
|
||||||
resourceCache.set(identifier, acrs);
|
|
||||||
}
|
|
||||||
const size = policies.length;
|
const size = policies.length;
|
||||||
policies.push(...this.getEffectivePolicies(target, acrs));
|
policies.push(...this.getEffectivePolicies(target, acrs));
|
||||||
this.logger.debug(`Found ${policies.length - size} policies relevant for ${target.path} in ${identifier.path}`);
|
this.logger.debug(`Found ${policies.length - size} policies relevant for ${target.path} in ${identifier.path}`);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import { concat } from '../util/IterableUtil';
|
import { concat } from '../util/IterableUtil';
|
||||||
import { IdentifierMap, IdentifierSetMultiMap } from '../util/map/IdentifierMap';
|
import { IdentifierMap, IdentifierSetMultiMap } from '../util/map/IdentifierMap';
|
||||||
|
import { getDefault } from '../util/map/MapUtil';
|
||||||
import { ensureTrailingSlash, trimTrailingSlashes } from '../util/PathUtil';
|
import { ensureTrailingSlash, trimTrailingSlashes } from '../util/PathUtil';
|
||||||
import type { PermissionReaderInput } from './PermissionReader';
|
import type { PermissionReaderInput } from './PermissionReader';
|
||||||
import { PermissionReader } from './PermissionReader';
|
import { PermissionReader } from './PermissionReader';
|
||||||
@ -44,11 +45,7 @@ export class PathBasedReader extends PermissionReader {
|
|||||||
for (const [ identifier, modes ] of accessMap) {
|
for (const [ identifier, modes ] of accessMap) {
|
||||||
const reader = this.findReader(identifier.path);
|
const reader = this.findReader(identifier.path);
|
||||||
if (reader) {
|
if (reader) {
|
||||||
let matches = result.get(reader);
|
const matches = getDefault(result, reader, (): AccessMap => new IdentifierSetMultiMap());
|
||||||
if (!matches) {
|
|
||||||
matches = new IdentifierSetMultiMap();
|
|
||||||
result.set(reader, matches);
|
|
||||||
}
|
|
||||||
matches.set(identifier, modes);
|
matches.set(identifier, modes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ export class UnionPermissionReader extends UnionHandler<PermissionReader> {
|
|||||||
private mergePermissionMaps(permissionMap: PermissionMap, result: PermissionMap): void {
|
private mergePermissionMaps(permissionMap: PermissionMap, result: PermissionMap): void {
|
||||||
for (const [ identifier, permissionSet ] of permissionMap) {
|
for (const [ identifier, permissionSet ] of permissionMap) {
|
||||||
for (const [ credential, permission ] of Object.entries(permissionSet) as [keyof PermissionSet, Permission][]) {
|
for (const [ credential, permission ] of Object.entries(permissionSet) as [keyof PermissionSet, Permission][]) {
|
||||||
const resultSet = getDefault(result, identifier, {});
|
const resultSet = getDefault(result, identifier, (): PermissionSet => ({}));
|
||||||
resultSet[credential] = this.mergePermissions(permission, resultSet[credential]);
|
resultSet[credential] = this.mergePermissions(permission, resultSet[credential]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import type { ValuePreferences } from '../../http/representation/RepresentationPreferences';
|
import type { ValuePreferences } from '../../http/representation/RepresentationPreferences';
|
||||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||||
|
import type { PromiseOrValue } from '../../util/PromiseUtil';
|
||||||
import { getConversionTarget, getTypeWeight, preferencesToString } from './ConversionUtil';
|
import { getConversionTarget, getTypeWeight, preferencesToString } from './ConversionUtil';
|
||||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||||
|
|
||||||
type PromiseOrValue<T> = T | Promise<T>;
|
|
||||||
type ValuePreferencesArg =
|
type ValuePreferencesArg =
|
||||||
PromiseOrValue<string> |
|
PromiseOrValue<string> |
|
||||||
PromiseOrValue<string[]> |
|
PromiseOrValue<string[]> |
|
||||||
|
@ -1,5 +1,29 @@
|
|||||||
|
import { types } from 'util';
|
||||||
import { createAggregateError } from './errors/HttpErrorUtil';
|
import { createAggregateError } from './errors/HttpErrorUtil';
|
||||||
|
|
||||||
|
export type PromiseOrValue<T> = T | Promise<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies if the given value is a Promise or not.
|
||||||
|
* @param object - Object to check.
|
||||||
|
*/
|
||||||
|
export function isPromise<T>(object: PromiseOrValue<T>): object is Promise<T> {
|
||||||
|
return types.isPromise(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls `callback` with the resolved value of `object`.
|
||||||
|
* In case `object` is a Promise, the result will also be a Promise,
|
||||||
|
* otherwise the result will be sync.
|
||||||
|
*/
|
||||||
|
export function resolvePromiseOrValue<TIn, TOut>(object: PromiseOrValue<TIn>, callback: (val: TIn) => TOut):
|
||||||
|
PromiseOrValue<TOut> {
|
||||||
|
if (isPromise(object)) {
|
||||||
|
return object.then((val): TOut => callback(val));
|
||||||
|
}
|
||||||
|
return callback(object);
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
function noop(): void {}
|
function noop(): void {}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import { isHttpRequest } from '../server/HttpRequest';
|
|||||||
import { InternalServerError } from './errors/InternalServerError';
|
import { InternalServerError } from './errors/InternalServerError';
|
||||||
import type { Guarded } from './GuardedStream';
|
import type { Guarded } from './GuardedStream';
|
||||||
import { guardStream } from './GuardedStream';
|
import { guardStream } from './GuardedStream';
|
||||||
|
import type { PromiseOrValue } from './PromiseUtil';
|
||||||
|
|
||||||
export const endOfStream = promisify(eos);
|
export const endOfStream = promisify(eos);
|
||||||
|
|
||||||
@ -119,12 +120,12 @@ export interface AsyncTransformOptions<T = any> extends DuplexOptions {
|
|||||||
/**
|
/**
|
||||||
* Transforms data from the source by calling the `push` method
|
* Transforms data from the source by calling the `push` method
|
||||||
*/
|
*/
|
||||||
transform?: (this: Transform, data: T, encoding: string) => any | Promise<any>;
|
transform?: (this: Transform, data: T, encoding: string) => PromiseOrValue<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs any final actions after the source has ended
|
* Performs any final actions after the source has ended
|
||||||
*/
|
*/
|
||||||
flush?: (this: Transform) => any | Promise<any>;
|
flush?: (this: Transform) => PromiseOrValue<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||||
|
import type { PromiseOrValue } from '../PromiseUtil';
|
||||||
import type { ReadWriteLocker } from './ReadWriteLocker';
|
import type { ReadWriteLocker } from './ReadWriteLocker';
|
||||||
import type { ResourceLocker } from './ResourceLocker';
|
import type { ResourceLocker } from './ResourceLocker';
|
||||||
|
|
||||||
@ -12,11 +13,11 @@ export class EqualReadWriteLocker implements ReadWriteLocker {
|
|||||||
this.locker = locker;
|
this.locker = locker;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withReadLock<T>(identifier: ResourceIdentifier, whileLocked: () => (Promise<T> | T)): Promise<T> {
|
public async withReadLock<T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>): Promise<T> {
|
||||||
return this.withLock(identifier, whileLocked);
|
return this.withLock(identifier, whileLocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withWriteLock<T>(identifier: ResourceIdentifier, whileLocked: () => (Promise<T> | T)): Promise<T> {
|
public async withWriteLock<T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>): Promise<T> {
|
||||||
return this.withLock(identifier, whileLocked);
|
return this.withLock(identifier, whileLocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ export class EqualReadWriteLocker implements ReadWriteLocker {
|
|||||||
* @param identifier - Identifier of resource that needs to be locked.
|
* @param identifier - Identifier of resource that needs to be locked.
|
||||||
* @param whileLocked - Function to resolve while the resource is locked.
|
* @param whileLocked - Function to resolve while the resource is locked.
|
||||||
*/
|
*/
|
||||||
private async withLock<T>(identifier: ResourceIdentifier, whileLocked: () => T | Promise<T>): Promise<T> {
|
private async withLock<T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>): Promise<T> {
|
||||||
await this.locker.acquire(identifier);
|
await this.locker.acquire(identifier);
|
||||||
try {
|
try {
|
||||||
return await whileLocked();
|
return await whileLocked();
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||||
|
import type { PromiseOrValue } from '../PromiseUtil';
|
||||||
import type { ReadWriteLocker } from './ReadWriteLocker';
|
import type { ReadWriteLocker } from './ReadWriteLocker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,7 +15,7 @@ export interface ExpiringReadWriteLocker extends ReadWriteLocker {
|
|||||||
* @param whileLocked - A function to execute while the resource is locked.
|
* @param whileLocked - A function to execute while the resource is locked.
|
||||||
* Receives a callback as input parameter to maintain the lock.
|
* Receives a callback as input parameter to maintain the lock.
|
||||||
*/
|
*/
|
||||||
withReadLock: <T>(identifier: ResourceIdentifier, whileLocked: (maintainLock: () => void) => T | Promise<T>)
|
withReadLock: <T>(identifier: ResourceIdentifier, whileLocked: (maintainLock: () => void) => PromiseOrValue<T>)
|
||||||
=> Promise<T>;
|
=> Promise<T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -26,6 +27,6 @@ export interface ExpiringReadWriteLocker extends ReadWriteLocker {
|
|||||||
* @param whileLocked - A function to execute while the resource is locked.
|
* @param whileLocked - A function to execute while the resource is locked.
|
||||||
* Receives a callback as input parameter to maintain the lock.
|
* Receives a callback as input parameter to maintain the lock.
|
||||||
*/
|
*/
|
||||||
withWriteLock: <T>(identifier: ResourceIdentifier, whileLocked: (maintainLock: () => void) => T | Promise<T>)
|
withWriteLock: <T>(identifier: ResourceIdentifier, whileLocked: (maintainLock: () => void) => PromiseOrValue<T>)
|
||||||
=> Promise<T>;
|
=> Promise<T>;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import type { ResourceIdentifier } from '../../http/representation/ResourceIdent
|
|||||||
import type { KeyValueStorage } from '../../storage/keyvalue/KeyValueStorage';
|
import type { KeyValueStorage } from '../../storage/keyvalue/KeyValueStorage';
|
||||||
import { ForbiddenHttpError } from '../errors/ForbiddenHttpError';
|
import { ForbiddenHttpError } from '../errors/ForbiddenHttpError';
|
||||||
import { InternalServerError } from '../errors/InternalServerError';
|
import { InternalServerError } from '../errors/InternalServerError';
|
||||||
|
import type { PromiseOrValue } from '../PromiseUtil';
|
||||||
import type { ReadWriteLocker } from './ReadWriteLocker';
|
import type { ReadWriteLocker } from './ReadWriteLocker';
|
||||||
import type { ResourceLocker } from './ResourceLocker';
|
import type { ResourceLocker } from './ResourceLocker';
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ export class GreedyReadWriteLocker implements ReadWriteLocker {
|
|||||||
this.suffixes = suffixes;
|
this.suffixes = suffixes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withReadLock<T>(identifier: ResourceIdentifier, whileLocked: () => (Promise<T> | T)): Promise<T> {
|
public async withReadLock<T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>): Promise<T> {
|
||||||
await this.preReadSetup(identifier);
|
await this.preReadSetup(identifier);
|
||||||
try {
|
try {
|
||||||
return await whileLocked();
|
return await whileLocked();
|
||||||
@ -52,7 +53,7 @@ export class GreedyReadWriteLocker implements ReadWriteLocker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withWriteLock<T>(identifier: ResourceIdentifier, whileLocked: () => (Promise<T> | T)): Promise<T> {
|
public async withWriteLock<T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>): Promise<T> {
|
||||||
if (identifier.path.endsWith(`.${this.suffixes.count}`)) {
|
if (identifier.path.endsWith(`.${this.suffixes.count}`)) {
|
||||||
throw new ForbiddenHttpError('This resource is used for internal purposes.');
|
throw new ForbiddenHttpError('This resource is used for internal purposes.');
|
||||||
}
|
}
|
||||||
@ -117,7 +118,7 @@ export class GreedyReadWriteLocker implements ReadWriteLocker {
|
|||||||
/**
|
/**
|
||||||
* Safely runs an action on the count.
|
* Safely runs an action on the count.
|
||||||
*/
|
*/
|
||||||
private async withInternalReadLock<T>(identifier: ResourceIdentifier, whileLocked: () => (Promise<T> | T)):
|
private async withInternalReadLock<T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>):
|
||||||
Promise<T> {
|
Promise<T> {
|
||||||
const read = this.getReadLockKey(identifier);
|
const read = this.getReadLockKey(identifier);
|
||||||
await this.locker.acquire(read);
|
await this.locker.acquire(read);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||||
|
import type { PromiseOrValue } from '../PromiseUtil';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows the locking of resources which is needed for non-atomic {@link ResourceStore}s.
|
* Allows the locking of resources which is needed for non-atomic {@link ResourceStore}s.
|
||||||
@ -14,7 +15,7 @@ export interface ReadWriteLocker {
|
|||||||
*
|
*
|
||||||
* @returns A promise resolving when the lock is released.
|
* @returns A promise resolving when the lock is released.
|
||||||
*/
|
*/
|
||||||
withReadLock: <T>(identifier: ResourceIdentifier, whileLocked: () => T | Promise<T>) => Promise<T>;
|
withReadLock: <T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>) => Promise<T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the given function while the resource is locked.
|
* Run the given function while the resource is locked.
|
||||||
@ -26,5 +27,5 @@ export interface ReadWriteLocker {
|
|||||||
*
|
*
|
||||||
* @returns A promise resolving when the lock is released.
|
* @returns A promise resolving when the lock is released.
|
||||||
*/
|
*/
|
||||||
withWriteLock: <T>(identifier: ResourceIdentifier, whileLocked: () => T | Promise<T>) => Promise<T>;
|
withWriteLock: <T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>) => Promise<T>;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import type { Initializable } from '../../init/Initializable';
|
|||||||
import { getLoggerFor } from '../../logging/LogUtil';
|
import { getLoggerFor } from '../../logging/LogUtil';
|
||||||
import type { AttemptSettings } from '../LockUtils';
|
import type { AttemptSettings } from '../LockUtils';
|
||||||
import { retryFunction } from '../LockUtils';
|
import { retryFunction } from '../LockUtils';
|
||||||
|
import type { PromiseOrValue } from '../PromiseUtil';
|
||||||
import type { ReadWriteLocker } from './ReadWriteLocker';
|
import type { ReadWriteLocker } from './ReadWriteLocker';
|
||||||
import type { ResourceLocker } from './ResourceLocker';
|
import type { ResourceLocker } from './ResourceLocker';
|
||||||
import type { RedisResourceLock, RedisReadWriteLock, RedisAnswer } from './scripts/RedisLuaScripts';
|
import type { RedisResourceLock, RedisReadWriteLock, RedisAnswer } from './scripts/RedisLuaScripts';
|
||||||
@ -127,7 +128,7 @@ export class RedisLocker implements ReadWriteLocker, ResourceLocker, Initializab
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withReadLock<T>(identifier: ResourceIdentifier, whileLocked: () => (Promise<T> | T)): Promise<T> {
|
public async withReadLock<T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>): Promise<T> {
|
||||||
const key = this.getReadWriteKey(identifier);
|
const key = this.getReadWriteKey(identifier);
|
||||||
await retryFunction(
|
await retryFunction(
|
||||||
this.swallowFalse(this.redisRw.acquireReadLock.bind(this.redisRw, key)),
|
this.swallowFalse(this.redisRw.acquireReadLock.bind(this.redisRw, key)),
|
||||||
@ -143,7 +144,7 @@ export class RedisLocker implements ReadWriteLocker, ResourceLocker, Initializab
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withWriteLock<T>(identifier: ResourceIdentifier, whileLocked: () => (Promise<T> | T)): Promise<T> {
|
public async withWriteLock<T>(identifier: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>): Promise<T> {
|
||||||
const key = this.getReadWriteKey(identifier);
|
const key = this.getReadWriteKey(identifier);
|
||||||
await retryFunction(
|
await retryFunction(
|
||||||
this.swallowFalse(this.redisRw.acquireWriteLock.bind(this.redisRw, key)),
|
this.swallowFalse(this.redisRw.acquireWriteLock.bind(this.redisRw, key)),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||||
import { getLoggerFor } from '../../logging/LogUtil';
|
import { getLoggerFor } from '../../logging/LogUtil';
|
||||||
|
import type { PromiseOrValue } from '../PromiseUtil';
|
||||||
import type { ExpiringReadWriteLocker } from './ExpiringReadWriteLocker';
|
import type { ExpiringReadWriteLocker } from './ExpiringReadWriteLocker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,14 +21,14 @@ export class VoidLocker implements ExpiringReadWriteLocker {
|
|||||||
|
|
||||||
public async withReadLock<T>(
|
public async withReadLock<T>(
|
||||||
identifier: ResourceIdentifier,
|
identifier: ResourceIdentifier,
|
||||||
whileLocked: (maintainLock: () => void) => T | Promise<T>,
|
whileLocked: (maintainLock: () => void) => PromiseOrValue<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return whileLocked(noop);
|
return whileLocked(noop);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withWriteLock<T>(
|
public async withWriteLock<T>(
|
||||||
identifier: ResourceIdentifier,
|
identifier: ResourceIdentifier,
|
||||||
whileLocked: (maintainLock: () => void) => T | Promise<T>,
|
whileLocked: (maintainLock: () => void) => PromiseOrValue<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return whileLocked(noop);
|
return whileLocked(noop);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||||
import { getLoggerFor } from '../../logging/LogUtil';
|
import { getLoggerFor } from '../../logging/LogUtil';
|
||||||
import { InternalServerError } from '../errors/InternalServerError';
|
import { InternalServerError } from '../errors/InternalServerError';
|
||||||
|
import type { PromiseOrValue } from '../PromiseUtil';
|
||||||
import type { ExpiringReadWriteLocker } from './ExpiringReadWriteLocker';
|
import type { ExpiringReadWriteLocker } from './ExpiringReadWriteLocker';
|
||||||
import type { ReadWriteLocker } from './ReadWriteLocker';
|
import type { ReadWriteLocker } from './ReadWriteLocker';
|
||||||
import Timeout = NodeJS.Timeout;
|
import Timeout = NodeJS.Timeout;
|
||||||
@ -24,12 +25,12 @@ export class WrappedExpiringReadWriteLocker implements ExpiringReadWriteLocker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async withReadLock<T>(identifier: ResourceIdentifier,
|
public async withReadLock<T>(identifier: ResourceIdentifier,
|
||||||
whileLocked: (maintainLock: () => void) => T | Promise<T>): Promise<T> {
|
whileLocked: (maintainLock: () => void) => PromiseOrValue<T>): Promise<T> {
|
||||||
return this.locker.withReadLock(identifier, async(): Promise<T> => this.expiringPromise(identifier, whileLocked));
|
return this.locker.withReadLock(identifier, async(): Promise<T> => this.expiringPromise(identifier, whileLocked));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async withWriteLock<T>(identifier: ResourceIdentifier,
|
public async withWriteLock<T>(identifier: ResourceIdentifier,
|
||||||
whileLocked: (maintainLock: () => void) => T | Promise<T>): Promise<T> {
|
whileLocked: (maintainLock: () => void) => PromiseOrValue<T>): Promise<T> {
|
||||||
return this.locker.withWriteLock(identifier, async(): Promise<T> => this.expiringPromise(identifier, whileLocked));
|
return this.locker.withWriteLock(identifier, async(): Promise<T> => this.expiringPromise(identifier, whileLocked));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ export class WrappedExpiringReadWriteLocker implements ExpiringReadWriteLocker {
|
|||||||
* it receives. The ResourceIdentifier is only used for logging.
|
* it receives. The ResourceIdentifier is only used for logging.
|
||||||
*/
|
*/
|
||||||
private async expiringPromise<T>(identifier: ResourceIdentifier,
|
private async expiringPromise<T>(identifier: ResourceIdentifier,
|
||||||
whileLocked: (maintainLock: () => void) => T | Promise<T>): Promise<T> {
|
whileLocked: (maintainLock: () => void) => PromiseOrValue<T>): Promise<T> {
|
||||||
let timer: Timeout;
|
let timer: Timeout;
|
||||||
let createTimeout: () => Timeout;
|
let createTimeout: () => Timeout;
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { resolvePromiseOrValue } from '../PromiseUtil';
|
||||||
|
import type { PromiseOrValue } from '../PromiseUtil';
|
||||||
import type { SetMultiMap } from './SetMultiMap';
|
import type { SetMultiMap } from './SetMultiMap';
|
||||||
|
|
||||||
export type MapKey<T> = T extends Map<infer TKey, any> ? TKey : never;
|
export type MapKey<T> = T extends Map<infer TKey, any> ? TKey : never;
|
||||||
@ -42,18 +44,33 @@ export function modify<T extends SetMultiMap<any, any>>(map: T, options: ModifyO
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the result of calling `map.get(key)`.
|
* Finds the result of calling `map.get(key)`.
|
||||||
* If there is no result, it instead returns the default value.
|
* If there is no result, it instead returns the result of the default function.
|
||||||
* The Map will also be updated to assign that default value to the given key.
|
* The Map will also be updated to assign that default value to the given key.
|
||||||
*
|
*
|
||||||
* @param map - Map to use.
|
* @param map - Map to use.
|
||||||
* @param key - Key to find the value for.
|
* @param key - Key to find the value for.
|
||||||
* @param defaultValue - Value to insert and return if no result was found.
|
* @param defaultFn - Function to generate default value to insert and return if no result was found.
|
||||||
*/
|
*/
|
||||||
export function getDefault<TKey, TValue>(map: Map<TKey, TValue>, key: TKey, defaultValue: TValue): TValue {
|
export function getDefault<TKey, TValue>(map: Map<TKey, TValue>, key: TKey, defaultFn: () => TValue): TValue;
|
||||||
|
/**
|
||||||
|
* Finds the result of calling `map.get(key)`.
|
||||||
|
* If there is no result, it instead returns the result of the default function.
|
||||||
|
* The Map will also be updated to assign the resolved default value to the given key.
|
||||||
|
*
|
||||||
|
* @param map - Map to use.
|
||||||
|
* @param key - Key to find the value for.
|
||||||
|
* @param defaultFn - Function to generate default value to insert and return if no result was found.
|
||||||
|
*/
|
||||||
|
export function getDefault<TKey, TValue>(map: Map<TKey, TValue>, key: TKey, defaultFn: () => Promise<TValue>):
|
||||||
|
Promise<TValue>;
|
||||||
|
export function getDefault<TKey, TValue>(map: Map<TKey, TValue>, key: TKey, defaultFn: () => PromiseOrValue<TValue>):
|
||||||
|
PromiseOrValue<TValue> {
|
||||||
const value = map.get(key);
|
const value = map.get(key);
|
||||||
if (value) {
|
if (value) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
map.set(key, defaultValue);
|
return resolvePromiseOrValue<TValue, TValue>(defaultFn(), (val): TValue => {
|
||||||
return defaultValue;
|
map.set(key, val);
|
||||||
|
return val;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import type { ResourceIdentifier } from '../../../src/http/representation/Resour
|
|||||||
import { LockingResourceStore } from '../../../src/storage/LockingResourceStore';
|
import { LockingResourceStore } from '../../../src/storage/LockingResourceStore';
|
||||||
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
||||||
import type { ExpiringReadWriteLocker } from '../../../src/util/locking/ExpiringReadWriteLocker';
|
import type { ExpiringReadWriteLocker } from '../../../src/util/locking/ExpiringReadWriteLocker';
|
||||||
|
import type { PromiseOrValue } from '../../../src/util/PromiseUtil';
|
||||||
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
|
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
|
||||||
import { flushPromises } from '../../util/Util';
|
import { flushPromises } from '../../util/Util';
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ describe('A LockingResourceStore', (): void => {
|
|||||||
|
|
||||||
locker = {
|
locker = {
|
||||||
withReadLock: jest.fn(async <T>(id: ResourceIdentifier,
|
withReadLock: jest.fn(async <T>(id: ResourceIdentifier,
|
||||||
whileLocked: (maintainLock: () => void) => T | Promise<T>): Promise<T> => {
|
whileLocked: (maintainLock: () => void) => PromiseOrValue<T>): Promise<T> => {
|
||||||
order.push('lock read');
|
order.push('lock read');
|
||||||
try {
|
try {
|
||||||
// Allows simulating a timeout event
|
// Allows simulating a timeout event
|
||||||
@ -61,7 +62,7 @@ describe('A LockingResourceStore', (): void => {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
withWriteLock: jest.fn(async <T>(identifier: ResourceIdentifier,
|
withWriteLock: jest.fn(async <T>(identifier: ResourceIdentifier,
|
||||||
whileLocked: (maintainLock: () => void) => T | Promise<T>): Promise<T> => {
|
whileLocked: (maintainLock: () => void) => PromiseOrValue<T>): Promise<T> => {
|
||||||
order.push('lock write');
|
order.push('lock write');
|
||||||
try {
|
try {
|
||||||
return await whileLocked(emptyFn);
|
return await whileLocked(emptyFn);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { ResourceIdentifier } from '../../../../src/http/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../../../src/http/representation/ResourceIdentifier';
|
||||||
import type { ReadWriteLocker } from '../../../../src/util/locking/ReadWriteLocker';
|
import type { ReadWriteLocker } from '../../../../src/util/locking/ReadWriteLocker';
|
||||||
import { WrappedExpiringReadWriteLocker } from '../../../../src/util/locking/WrappedExpiringReadWriteLocker';
|
import { WrappedExpiringReadWriteLocker } from '../../../../src/util/locking/WrappedExpiringReadWriteLocker';
|
||||||
|
import type { PromiseOrValue } from '../../../../src/util/PromiseUtil';
|
||||||
|
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|
||||||
@ -14,9 +15,9 @@ describe('A WrappedExpiringReadWriteLocker', (): void => {
|
|||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
wrappedLocker = {
|
wrappedLocker = {
|
||||||
withReadLock: jest.fn(async<T>(id: ResourceIdentifier, whileLocked: () => T | Promise<T>):
|
withReadLock: jest.fn(async<T>(id: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>):
|
||||||
Promise<T> => whileLocked()),
|
Promise<T> => whileLocked()),
|
||||||
withWriteLock: jest.fn(async<T>(id: ResourceIdentifier, whileLocked: () => T | Promise<T>):
|
withWriteLock: jest.fn(async<T>(id: ResourceIdentifier, whileLocked: () => PromiseOrValue<T>):
|
||||||
Promise<T> => whileLocked()),
|
Promise<T> => whileLocked()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,12 +44,17 @@ describe('MapUtil', (): void => {
|
|||||||
describe('#getDefault', (): void => {
|
describe('#getDefault', (): void => {
|
||||||
it('returns the value it finds in the Map for the given key.', async(): Promise<void> => {
|
it('returns the value it finds in the Map for the given key.', async(): Promise<void> => {
|
||||||
const map = new Map([[ key1, 123 ]]);
|
const map = new Map([[ key1, 123 ]]);
|
||||||
expect(getDefault(map, key1, 999)).toBe(123);
|
expect(getDefault(map, key1, (): number => 999)).toBe(123);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the default value if it finds no value for the given key.', async(): Promise<void> => {
|
it('returns the default value if it finds no value for the given key.', async(): Promise<void> => {
|
||||||
const map = new Map([[ key1, 123 ]]);
|
const map = new Map([[ key1, 123 ]]);
|
||||||
expect(getDefault(map, key2, 999)).toBe(999);
|
expect(getDefault(map, key2, (): number => 999)).toBe(999);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle async default functions.', async(): Promise<void> => {
|
||||||
|
const map = new Map([[ key1, 123 ]]);
|
||||||
|
await expect(getDefault(map, key2, async(): Promise<number> => 999)).resolves.toBe(999);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user