mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Expose a storage description resource for storage containers
This commit is contained in:
43
src/server/description/StaticStorageDescriber.ts
Normal file
43
src/server/description/StaticStorageDescriber.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { NamedNode, Quad, Quad_Object, Term } from '@rdfjs/types';
|
||||
import { DataFactory } from 'n3';
|
||||
import { stringToTerm } from 'rdf-string';
|
||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||
import { StorageDescriber } from './StorageDescriber';
|
||||
import quad = DataFactory.quad;
|
||||
import namedNode = DataFactory.namedNode;
|
||||
|
||||
/**
|
||||
* Adds a fixed set of triples to the storage description resource,
|
||||
* with the resource identifier as subject.
|
||||
*/
|
||||
export class StaticStorageDescriber extends StorageDescriber {
|
||||
private readonly terms: ReadonlyMap<NamedNode, Quad_Object[]>;
|
||||
|
||||
public constructor(terms: Record<string, string | string[]>) {
|
||||
super();
|
||||
const termMap = new Map<NamedNode, Quad_Object[]>();
|
||||
for (const [ predicate, objects ] of Object.entries(terms)) {
|
||||
const predTerm = stringToTerm(predicate);
|
||||
if (predTerm.termType !== 'NamedNode') {
|
||||
throw new Error('Predicate needs to be a named node.');
|
||||
}
|
||||
const objTerms = (Array.isArray(objects) ? objects : [ objects ]).map((obj): Term => stringToTerm(obj));
|
||||
// `stringToTerm` can only generate valid term types
|
||||
termMap.set(predTerm, objTerms as Quad_Object[]);
|
||||
}
|
||||
this.terms = termMap;
|
||||
}
|
||||
|
||||
public async handle(target: ResourceIdentifier): Promise<Quad[]> {
|
||||
const subject = namedNode(target.path);
|
||||
return [ ...this.generateTriples(subject) ];
|
||||
}
|
||||
|
||||
private* generateTriples(subject: NamedNode): Iterable<Quad> {
|
||||
for (const [ predicate, objects ] of this.terms.entries()) {
|
||||
for (const object of objects) {
|
||||
yield quad(subject, predicate, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/server/description/StorageDescriber.ts
Normal file
8
src/server/description/StorageDescriber.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Quad } from '@rdfjs/types';
|
||||
import type { ResourceIdentifier } from '../../http/representation/ResourceIdentifier';
|
||||
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
||||
|
||||
/**
|
||||
* Generates Quads that need to be added to the given storage description resource.
|
||||
*/
|
||||
export abstract class StorageDescriber extends AsyncHandler<ResourceIdentifier, Quad[]> {}
|
||||
58
src/server/description/StorageDescriptionHandler.ts
Normal file
58
src/server/description/StorageDescriptionHandler.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { OkResponseDescription } from '../../http/output/response/OkResponseDescription';
|
||||
import type { ResponseDescription } from '../../http/output/response/ResponseDescription';
|
||||
import { BasicRepresentation } from '../../http/representation/BasicRepresentation';
|
||||
import type { RepresentationConverter } from '../../storage/conversion/RepresentationConverter';
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { INTERNAL_QUADS } from '../../util/ContentTypes';
|
||||
import { MethodNotAllowedHttpError } from '../../util/errors/MethodNotAllowedHttpError';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { ensureTrailingSlash } from '../../util/PathUtil';
|
||||
import { PIM, RDF } from '../../util/Vocabularies';
|
||||
import type { OperationHttpHandlerInput } from '../OperationHttpHandler';
|
||||
import { OperationHttpHandler } from '../OperationHttpHandler';
|
||||
import type { StorageDescriber } from './StorageDescriber';
|
||||
|
||||
/**
|
||||
* Generates the response for GET requests targeting a storage description resource.
|
||||
* The suffix needs to match the suffix used to generate storage description resources
|
||||
* and will be used to verify the container it is linked to is an actual storage.
|
||||
*/
|
||||
export class StorageDescriptionHandler extends OperationHttpHandler {
|
||||
private readonly store: ResourceStore;
|
||||
private readonly suffix: string;
|
||||
private readonly converter: RepresentationConverter;
|
||||
private readonly describer: StorageDescriber;
|
||||
|
||||
public constructor(store: ResourceStore, suffix: string, converter: RepresentationConverter,
|
||||
describer: StorageDescriber) {
|
||||
super();
|
||||
this.store = store;
|
||||
this.suffix = suffix;
|
||||
this.converter = converter;
|
||||
this.describer = describer;
|
||||
}
|
||||
|
||||
public async canHandle({ operation: { target, method }}: OperationHttpHandlerInput): Promise<void> {
|
||||
if (method !== 'GET') {
|
||||
throw new MethodNotAllowedHttpError([ method ], `Only GET requests can target the storage description.`);
|
||||
}
|
||||
const container = { path: ensureTrailingSlash(target.path.slice(0, -this.suffix.length)) };
|
||||
const representation = await this.store.getRepresentation(container, {});
|
||||
representation.data.destroy();
|
||||
if (!representation.metadata.has(RDF.terms.type, PIM.terms.Storage)) {
|
||||
throw new NotImplementedHttpError(`Only supports descriptions of storage containers.`);
|
||||
}
|
||||
|
||||
await this.describer.canHandle(target);
|
||||
}
|
||||
|
||||
public async handle({ operation: { target, preferences }}: OperationHttpHandlerInput): Promise<ResponseDescription> {
|
||||
const quads = await this.describer.handle(target);
|
||||
|
||||
const representation = new BasicRepresentation(quads, INTERNAL_QUADS);
|
||||
|
||||
const converted = await this.converter.handleSafe({ identifier: target, representation, preferences });
|
||||
|
||||
return new OkResponseDescription(converted.metadata, converted.data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user