mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: regexRoutes stored as ordered array
This commit is contained in:
parent
afed963a23
commit
5399e75ae4
@ -24,7 +24,7 @@ The following changes pertain to the imports in the default configs:
|
|||||||
- All default configurations with a file-based backend now use a file-based locker instead of a memory-based one,
|
- All default configurations with a file-based backend now use a file-based locker instead of a memory-based one,
|
||||||
making them threadsafe.
|
making them threadsafe.
|
||||||
|
|
||||||
The following changes are relevant for v3 custom configs that replaced certain features.
|
The following changes are relevant for v4 custom configs that replaced certain features.
|
||||||
- `config/app/variables/cli.json` was changed to support the new `YargsCliExtractor` format.
|
- `config/app/variables/cli.json` was changed to support the new `YargsCliExtractor` format.
|
||||||
- `config/util/resource-locker/memory.json` had the locker @type changed from `SingleThreadedResourceLocker` to `MemoryResourceLocker`.
|
- `config/util/resource-locker/memory.json` had the locker @type changed from `SingleThreadedResourceLocker` to `MemoryResourceLocker`.
|
||||||
- The content-length parser has been moved from the default configuration to the quota configurations.
|
- The content-length parser has been moved from the default configuration to the quota configurations.
|
||||||
@ -33,6 +33,9 @@ The following changes are relevant for v3 custom configs that replaced certain f
|
|||||||
- `/storage/backend/quota/quota-file.json`
|
- `/storage/backend/quota/quota-file.json`
|
||||||
- The structure of the init configs has changed significantly to support worker threads.
|
- The structure of the init configs has changed significantly to support worker threads.
|
||||||
- `/app/init/*`
|
- `/app/init/*`
|
||||||
|
- RegexPathRouting has changed from a map datastructure to an array datastructure, allowing for fallthrough regex parsing. The change is reflected in the following default configs:
|
||||||
|
- `/storage/backend/regex.json`
|
||||||
|
- `/sparql-file-storage.json`
|
||||||
|
|
||||||
### Interface changes
|
### Interface changes
|
||||||
These changes are relevant if you wrote custom modules for the server that depend on existing interfaces.
|
These changes are relevant if you wrote custom modules for the server that depend on existing interfaces.
|
||||||
|
@ -51,16 +51,18 @@
|
|||||||
"@id": "urn:solid-server:default:RouterRule",
|
"@id": "urn:solid-server:default:RouterRule",
|
||||||
"@type": "RegexRouterRule",
|
"@type": "RegexRouterRule",
|
||||||
"base": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
"base": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||||
"storeMap": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"comment": "Internal storage data",
|
"comment": "Internal storage data",
|
||||||
"RegexRouterRule:_storeMap_key": "^/\\.internal/",
|
"@type": "RegexRule",
|
||||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:FileResourceStore" }
|
"regex": "^/\\.internal/",
|
||||||
|
"store": { "@id": "urn:solid-server:default:FileResourceStore" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"comment": "Send everything else to the SPARQL store.",
|
"comment": "Send everything else to the SPARQL store.",
|
||||||
"RegexRouterRule:_storeMap_key": "^/(?!\\.internal/).*",
|
"@type": "RegexRule",
|
||||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
"regex": ".*",
|
||||||
|
"store": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -21,22 +21,26 @@
|
|||||||
"@id": "urn:solid-server:default:RouterRule",
|
"@id": "urn:solid-server:default:RouterRule",
|
||||||
"@type": "RegexRouterRule",
|
"@type": "RegexRouterRule",
|
||||||
"base": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
"base": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||||
"storeMap": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"RegexRouterRule:_storeMap_key": "^/(\\.acl)?$",
|
"@type": "RegexRule",
|
||||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
"regex": "^/(\\.acl)?$",
|
||||||
|
"store": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"RegexRouterRule:_storeMap_key": "/file/",
|
"@type": "RegexRule",
|
||||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:FileResourceStore" }
|
"regex": "/file/",
|
||||||
|
"store": { "@id": "urn:solid-server:default:FileResourceStore" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"RegexRouterRule:_storeMap_key": "/memory/",
|
"@type": "RegexRule",
|
||||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:MemoryResourceStore" }
|
"regex": "/memory/",
|
||||||
|
"store": { "@id": "urn:solid-server:default:MemoryResourceStore" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"RegexRouterRule:_storeMap_key": "/sparql/",
|
"@type": "RegexRule",
|
||||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
"regex": "/sparql/",
|
||||||
|
"store": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,19 @@ import { trimTrailingSlashes } from '../../util/PathUtil';
|
|||||||
import type { ResourceStore } from '../ResourceStore';
|
import type { ResourceStore } from '../ResourceStore';
|
||||||
import { RouterRule } from './RouterRule';
|
import { RouterRule } from './RouterRule';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to easily configure Regex to ResourceStore mappings in the config files.
|
||||||
|
*/
|
||||||
|
export class RegexRule {
|
||||||
|
public readonly regex: RegExp;
|
||||||
|
public readonly store: ResourceStore;
|
||||||
|
|
||||||
|
public constructor(regex: string, store: ResourceStore) {
|
||||||
|
this.regex = new RegExp(regex, 'u');
|
||||||
|
this.store = store;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routes requests to a store based on the path of the identifier.
|
* Routes requests to a store based on the path of the identifier.
|
||||||
* The identifier will be stripped of the base URI after which regexes will be used to find the correct store.
|
* The identifier will be stripped of the base URI after which regexes will be used to find the correct store.
|
||||||
@ -16,16 +29,15 @@ import { RouterRule } from './RouterRule';
|
|||||||
*/
|
*/
|
||||||
export class RegexRouterRule extends RouterRule {
|
export class RegexRouterRule extends RouterRule {
|
||||||
private readonly base: string;
|
private readonly base: string;
|
||||||
private readonly regexes: Map<RegExp, ResourceStore>;
|
private readonly rules: RegexRule[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The keys of the `storeMap` will be converted into actual RegExp objects that will be used for testing.
|
* The keys of the `storeMap` will be converted into actual RegExp objects that will be used for testing.
|
||||||
*/
|
*/
|
||||||
public constructor(base: string, storeMap: Record<string, ResourceStore>) {
|
public constructor(base: string, rules: RegexRule[]) {
|
||||||
super();
|
super();
|
||||||
this.base = trimTrailingSlashes(base);
|
this.base = trimTrailingSlashes(base);
|
||||||
this.regexes = new Map(Object.keys(storeMap).map((regex): [ RegExp, ResourceStore ] =>
|
this.rules = rules;
|
||||||
[ new RegExp(regex, 'u'), storeMap[regex] ]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async canHandle(input: { identifier: ResourceIdentifier; representation?: Representation }): Promise<void> {
|
public async canHandle(input: { identifier: ResourceIdentifier; representation?: Representation }): Promise<void> {
|
||||||
@ -42,9 +54,9 @@ export class RegexRouterRule extends RouterRule {
|
|||||||
*/
|
*/
|
||||||
private matchStore(identifier: ResourceIdentifier): ResourceStore {
|
private matchStore(identifier: ResourceIdentifier): ResourceStore {
|
||||||
const path = this.toRelative(identifier);
|
const path = this.toRelative(identifier);
|
||||||
for (const regex of this.regexes.keys()) {
|
for (const { regex, store } of this.rules) {
|
||||||
if (regex.test(path)) {
|
if (regex.test(path)) {
|
||||||
return this.regexes.get(regex)!;
|
return store;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new NotImplementedHttpError(`No stored regexes match ${identifier.path}`);
|
throw new NotImplementedHttpError(`No stored regexes match ${identifier.path}`);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { ResourceStore } from '../../../../src/storage/ResourceStore';
|
import type { ResourceStore } from '../../../../src/storage/ResourceStore';
|
||||||
import { RegexRouterRule } from '../../../../src/storage/routing/RegexRouterRule';
|
import { RegexRouterRule, RegexRule } from '../../../../src/storage/routing/RegexRouterRule';
|
||||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||||
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
||||||
|
|
||||||
@ -7,29 +7,36 @@ describe('A RegexRouterRule', (): void => {
|
|||||||
const base = 'http://test.com/';
|
const base = 'http://test.com/';
|
||||||
const store: ResourceStore = 'resourceStore' as any;
|
const store: ResourceStore = 'resourceStore' as any;
|
||||||
|
|
||||||
|
it('can construct a RegexRule utility object.', (): void => {
|
||||||
|
const regex = '/myPath/';
|
||||||
|
const rule = new RegexRule(regex, store);
|
||||||
|
expect(rule.regex).toEqual(new RegExp(regex, 'u'));
|
||||||
|
expect(rule.store).toEqual(store);
|
||||||
|
});
|
||||||
|
|
||||||
it('rejects identifiers not containing the base.', async(): Promise<void> => {
|
it('rejects identifiers not containing the base.', async(): Promise<void> => {
|
||||||
const router = new RegexRouterRule(base, {});
|
const router = new RegexRouterRule(base, []);
|
||||||
const result = router.canHandle({ identifier: { path: 'http://notTest.com/apple' }});
|
const result = router.canHandle({ identifier: { path: 'http://notTest.com/apple' }});
|
||||||
await expect(result).rejects.toThrow(BadRequestHttpError);
|
await expect(result).rejects.toThrow(BadRequestHttpError);
|
||||||
await expect(result).rejects.toThrow('Identifiers need to start with http://test.com');
|
await expect(result).rejects.toThrow('Identifiers need to start with http://test.com');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects identifiers not matching any regex.', async(): Promise<void> => {
|
it('rejects identifiers not matching any regex.', async(): Promise<void> => {
|
||||||
const router = new RegexRouterRule(base, { pear: store });
|
const router = new RegexRouterRule(base, [ new RegexRule('pear', store) ]);
|
||||||
const result = router.canHandle({ identifier: { path: `${base}apple/` }});
|
const result = router.canHandle({ identifier: { path: `${base}apple/` }});
|
||||||
await expect(result).rejects.toThrow(NotImplementedHttpError);
|
await expect(result).rejects.toThrow(NotImplementedHttpError);
|
||||||
await expect(result).rejects.toThrow('No stored regexes match http://test.com/apple/');
|
await expect(result).rejects.toThrow('No stored regexes match http://test.com/apple/');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts identifiers matching any regex.', async(): Promise<void> => {
|
it('accepts identifiers matching any regex.', async(): Promise<void> => {
|
||||||
const router = new RegexRouterRule(base, { '^/apple': store });
|
const router = new RegexRouterRule(base, [ new RegexRule('^/apple', store) ]);
|
||||||
await expect(router.canHandle({ identifier: { path: `${base}apple/` }}))
|
await expect(router.canHandle({ identifier: { path: `${base}apple/` }}))
|
||||||
.resolves.toBeUndefined();
|
.resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the corresponding store.', async(): Promise<void> => {
|
it('returns the corresponding store.', async(): Promise<void> => {
|
||||||
const store2: ResourceStore = 'resourceStore2' as any;
|
const store2: ResourceStore = 'resourceStore2' as any;
|
||||||
const router = new RegexRouterRule(base, { '^/apple': store2, '/pear/': store });
|
const router = new RegexRouterRule(base, [ new RegexRule('^/apple', store2), new RegexRule('/pear/', store) ]);
|
||||||
await expect(router.handle({ identifier: { path: `${base}apple/` }})).resolves.toBe(store2);
|
await expect(router.handle({ identifier: { path: `${base}apple/` }})).resolves.toBe(store2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user