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,
|
||||
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/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.
|
||||
@ -33,6 +33,9 @@ The following changes are relevant for v3 custom configs that replaced certain f
|
||||
- `/storage/backend/quota/quota-file.json`
|
||||
- The structure of the init configs has changed significantly to support worker threads.
|
||||
- `/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
|
||||
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",
|
||||
"@type": "RegexRouterRule",
|
||||
"base": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"storeMap": [
|
||||
"rules": [
|
||||
{
|
||||
"comment": "Internal storage data",
|
||||
"RegexRouterRule:_storeMap_key": "^/\\.internal/",
|
||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:FileResourceStore" }
|
||||
"@type": "RegexRule",
|
||||
"regex": "^/\\.internal/",
|
||||
"store": { "@id": "urn:solid-server:default:FileResourceStore" }
|
||||
},
|
||||
{
|
||||
"comment": "Send everything else to the SPARQL store.",
|
||||
"RegexRouterRule:_storeMap_key": "^/(?!\\.internal/).*",
|
||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||
"@type": "RegexRule",
|
||||
"regex": ".*",
|
||||
"store": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -21,22 +21,26 @@
|
||||
"@id": "urn:solid-server:default:RouterRule",
|
||||
"@type": "RegexRouterRule",
|
||||
"base": { "@id": "urn:solid-server:default:variable:baseUrl" },
|
||||
"storeMap": [
|
||||
"rules": [
|
||||
{
|
||||
"RegexRouterRule:_storeMap_key": "^/(\\.acl)?$",
|
||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||
"@type": "RegexRule",
|
||||
"regex": "^/(\\.acl)?$",
|
||||
"store": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||
},
|
||||
{
|
||||
"RegexRouterRule:_storeMap_key": "/file/",
|
||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:FileResourceStore" }
|
||||
"@type": "RegexRule",
|
||||
"regex": "/file/",
|
||||
"store": { "@id": "urn:solid-server:default:FileResourceStore" }
|
||||
},
|
||||
{
|
||||
"RegexRouterRule:_storeMap_key": "/memory/",
|
||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:MemoryResourceStore" }
|
||||
"@type": "RegexRule",
|
||||
"regex": "/memory/",
|
||||
"store": { "@id": "urn:solid-server:default:MemoryResourceStore" }
|
||||
},
|
||||
{
|
||||
"RegexRouterRule:_storeMap_key": "/sparql/",
|
||||
"RegexRouterRule:_storeMap_value": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||
"@type": "RegexRule",
|
||||
"regex": "/sparql/",
|
||||
"store": { "@id": "urn:solid-server:default:SparqlResourceStore" }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -6,6 +6,19 @@ import { trimTrailingSlashes } from '../../util/PathUtil';
|
||||
import type { ResourceStore } from '../ResourceStore';
|
||||
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.
|
||||
* 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 {
|
||||
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.
|
||||
*/
|
||||
public constructor(base: string, storeMap: Record<string, ResourceStore>) {
|
||||
public constructor(base: string, rules: RegexRule[]) {
|
||||
super();
|
||||
this.base = trimTrailingSlashes(base);
|
||||
this.regexes = new Map(Object.keys(storeMap).map((regex): [ RegExp, ResourceStore ] =>
|
||||
[ new RegExp(regex, 'u'), storeMap[regex] ]));
|
||||
this.rules = rules;
|
||||
}
|
||||
|
||||
public async canHandle(input: { identifier: ResourceIdentifier; representation?: Representation }): Promise<void> {
|
||||
@ -42,9 +54,9 @@ export class RegexRouterRule extends RouterRule {
|
||||
*/
|
||||
private matchStore(identifier: ResourceIdentifier): ResourceStore {
|
||||
const path = this.toRelative(identifier);
|
||||
for (const regex of this.regexes.keys()) {
|
||||
for (const { regex, store } of this.rules) {
|
||||
if (regex.test(path)) {
|
||||
return this.regexes.get(regex)!;
|
||||
return store;
|
||||
}
|
||||
}
|
||||
throw new NotImplementedHttpError(`No stored regexes match ${identifier.path}`);
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
||||
|
||||
@ -7,29 +7,36 @@ describe('A RegexRouterRule', (): void => {
|
||||
const base = 'http://test.com/';
|
||||
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> => {
|
||||
const router = new RegexRouterRule(base, {});
|
||||
const router = new RegexRouterRule(base, []);
|
||||
const result = router.canHandle({ identifier: { path: 'http://notTest.com/apple' }});
|
||||
await expect(result).rejects.toThrow(BadRequestHttpError);
|
||||
await expect(result).rejects.toThrow('Identifiers need to start with http://test.com');
|
||||
});
|
||||
|
||||
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/` }});
|
||||
await expect(result).rejects.toThrow(NotImplementedHttpError);
|
||||
await expect(result).rejects.toThrow('No stored regexes match http://test.com/apple/');
|
||||
});
|
||||
|
||||
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/` }}))
|
||||
.resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns the corresponding store.', async(): Promise<void> => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user