Attempt to do access control

main
Ailin Luca 2 years ago
parent 265b3e1c64
commit 6d8ff2515f
  1. 2
      package-lock.json
  2. 4
      packages/demo-react/src/Layout.tsx
  3. 59
      packages/demo-react/src/dashboard/BuildMainContainer.tsx
  4. 37
      packages/demo-react/src/dashboard/BuildRootContainer.tsx
  5. 8
      packages/demo-react/src/dashboard/Dashboard.tsx
  6. 1
      packages/solid/package.json
  7. 7
      packages/solid/src/ResourceStore.ts
  8. 51
      packages/solid/src/requester/requestResults/AccessRule.ts
  9. 33
      packages/solid/src/requester/requestResults/ErrorResult.ts
  10. 4
      packages/solid/src/requester/requests/checkRootContainer.ts
  11. 14
      packages/solid/src/requester/requests/createDataResource.ts
  12. 21
      packages/solid/src/requester/requests/getAccessRules.ts
  13. 2
      packages/solid/src/requester/requests/requestParams.ts
  14. 80
      packages/solid/src/requester/requests/setAccessRules.ts
  15. 4
      packages/solid/src/requester/requests/uploadResource.ts
  16. 128
      packages/solid/src/resource/Container.ts
  17. 43
      packages/solid/src/resource/Leaf.ts
  18. 74
      packages/solid/src/resource/Resource.ts
  19. 16
      packages/solid/src/util/rdfUtils.ts

2
package-lock.json generated

@ -36194,6 +36194,7 @@
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@inrupt/solid-client": "^1.30.0",
"@ldo/dataset": "^0.0.0",
"@ldo/ldo": "^0.0.0",
"@ldo/rdf-utils": "^0.0.0",
@ -46960,6 +46961,7 @@
"@ldo/solid": {
"version": "file:packages/solid",
"requires": {
"@inrupt/solid-client": "*",
"@inrupt/solid-client-authn-core": "^1.17.1",
"@ldo/cli": "^0.0.0",
"@ldo/dataset": "^0.0.0",

@ -4,12 +4,12 @@ import type { FunctionComponent } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { Dashboard } from "./dashboard/Dashboard";
import { Media } from "./media/Media";
import { BuildRootContainer } from "./dashboard/BuildRootContainer";
import { BuildMainContainer } from "./dashboard/BuildMainContainer";
const router = createBrowserRouter([
{
path: "/",
element: <BuildRootContainer child={Dashboard} />,
element: <BuildMainContainer child={Dashboard} />,
},
{
path: "/media/:uri",

@ -0,0 +1,59 @@
import React, { useState, useEffect } from "react";
import type { FunctionComponent } from "react";
import type { Container, LeafUri } from "@ldo/solid";
import { useSolidAuth, useLdo } from "@ldo/solid-react";
export interface BuildMainContainerChildProps {
mainContainer: Container;
}
export const BuildMainContainer: FunctionComponent<{
child: FunctionComponent<BuildMainContainerChildProps>;
}> = ({ child }) => {
const Child = child;
const [mainContainer, setMainContainer] = useState<Container | undefined>();
const { session } = useSolidAuth();
const { getResource } = useLdo();
useEffect(() => {
if (session.webId) {
const webIdResource = getResource(session.webId as LeafUri);
webIdResource.getRootContainer().then(async (rootContainer) => {
if (rootContainer.type === "error") {
alert(rootContainer.message);
return;
}
const mainContainer =
await rootContainer.createChildIfAbsent("demo-react/");
if (mainContainer.type === "error") {
alert(mainContainer.message);
return;
}
setMainContainer(mainContainer);
mainContainer.setAccessRules({
public: {
read: true,
write: false,
append: false,
control: false,
},
agent: {
[session.webId!]: {
read: true,
write: true,
append: true,
control: true,
},
},
});
});
}
}, [session.webId]);
if (!session.webId || !mainContainer) {
// Return blank screen
return <p>Loading</p>;
}
return <Child mainContainer={mainContainer} />;
};

@ -1,37 +0,0 @@
import React, { useState, useEffect } from "react";
import type { FunctionComponent } from "react";
import type { Container, LeafUri } from "@ldo/solid";
import { useSolidAuth, useLdo } from "@ldo/solid-react";
export interface BuildRootContainerChildProps {
rootContainer: Container;
}
export const BuildRootContainer: FunctionComponent<{
child: FunctionComponent<BuildRootContainerChildProps>;
}> = ({ child }) => {
const Child = child;
const [rootContainer, setRootContainer] = useState<Container | undefined>();
const { session } = useSolidAuth();
const { getResource } = useLdo();
useEffect(() => {
if (session.webId) {
const webIdResource = getResource(session.webId as LeafUri);
webIdResource.getRootContainer().then((rootContainer) => {
if (rootContainer.type !== "error") {
setRootContainer(rootContainer);
} else {
alert(rootContainer.message);
}
});
}
}, [session.webId]);
if (!session.webId || !rootContainer) {
// Return blank screen
return <p>Loading</p>;
}
return <Child rootContainer={rootContainer} />;
};

@ -1,11 +1,11 @@
import React from "react";
import type { FunctionComponent } from "react";
import type { BuildRootContainerChildProps } from "./BuildRootContainer";
import type { BuildMainContainerChildProps } from "./BuildMainContainer";
export const Dashboard: FunctionComponent<BuildRootContainerChildProps> = ({
rootContainer,
export const Dashboard: FunctionComponent<BuildMainContainerChildProps> = ({
mainContainer,
}) => {
return <p>{rootContainer.uri}</p>;
return <p>{mainContainer.uri}</p>;
// return (
// <div>
// <div>

@ -37,6 +37,7 @@
"typed-emitter": "^2.1.0"
},
"dependencies": {
"@inrupt/solid-client": "^1.30.0",
"@ldo/dataset": "^0.0.0",
"@ldo/ldo": "^0.0.0",
"@ldo/rdf-utils": "^0.0.0",

@ -1,6 +1,5 @@
import { Container } from "./resource/Container";
import { Leaf } from "./resource/Leaf";
import type { Resource } from "./resource/Resource";
import type { SolidLdoDatasetContext } from "./SolidLdoDatasetContext";
import type { ContainerUri, LeafUri } from "./util/uriTypes";
import { isContainerUri } from "./util/uriTypes";
@ -10,7 +9,7 @@ export interface ResourceGetterOptions {
}
export class ResourceStore {
protected resourceMap: Map<string, Resource>;
protected resourceMap: Map<string, Leaf | Container>;
protected context: SolidLdoDatasetContext;
constructor(context: SolidLdoDatasetContext) {
@ -20,8 +19,8 @@ export class ResourceStore {
get(uri: ContainerUri, options?: ResourceGetterOptions): Container;
get(uri: LeafUri, options?: ResourceGetterOptions): Leaf;
get(uri: string, options?: ResourceGetterOptions): Resource;
get(uri: string, options?: ResourceGetterOptions): Resource {
get(uri: string, options?: ResourceGetterOptions): Leaf | Container;
get(uri: string, options?: ResourceGetterOptions): Leaf | Container {
// Normalize URI by removing hash
const url = new URL(uri);
url.hash = "";

@ -0,0 +1,51 @@
import type { Access } from "@inrupt/solid-client";
import { ErrorResult } from "./ErrorResult";
import { RequesterResult } from "./RequesterResult";
export interface AccessRule {
public?: Access;
agent?: Record<string, Access>;
}
export class AccessRuleChangeResult
extends RequesterResult
implements AccessRule
{
type = "accessRuleChange" as const;
readonly public?: Access;
readonly agent?: Record<string, Access>;
constructor(
uri: string,
publicRules?: Access,
agentRules?: Record<string, Access>,
) {
super(uri);
this.public = publicRules;
this.agent = agentRules;
}
}
export class AccessRuleResult extends RequesterResult implements AccessRule {
type = "accessRule" as const;
readonly public: Access;
readonly agent: Record<string, Access>;
constructor(
uri: string,
publicRules: Access,
agentRules: Record<string, Access>,
) {
super(uri);
this.public = publicRules;
this.agent = agentRules;
}
}
export class AccessRuleFetchError extends ErrorResult {
readonly errorType = "accessRuleFetch" as const;
constructor(uri: string, message?: string) {
super(uri, message || `Cannot get access rules for ${uri}.`);
}
}

@ -11,7 +11,7 @@ export abstract class ErrorResult extends Error {
export class UnexpectedError extends ErrorResult {
error: Error;
readonly errorType = "unexpected";
readonly errorType = "unexpected" as const;
constructor(uri: string, error: Error) {
super(uri, error.message);
@ -31,3 +31,34 @@ export class UnexpectedError extends ErrorResult {
}
}
}
export class AggregateError<ErrorType extends ErrorResult> extends ErrorResult {
readonly errorType = "aggregate" as const;
readonly errors: ErrorType[];
constructor(
uri: string,
errors: (ErrorType | AggregateError<ErrorType>)[],
message?: string,
) {
const allErrors: ErrorType[] = [];
errors.forEach((error) => {
if (error instanceof AggregateError) {
error.errors.forEach((subError) => {
allErrors.push(subError);
});
} else {
allErrors.push(error);
}
});
super(
uri,
message ||
`Encountered multiple errors:${allErrors.reduce(
(agg, cur) => `${agg}\n${cur}`,
"",
)}`,
);
this.errors = allErrors;
}
}

@ -3,7 +3,7 @@ import {
type HttpErrorResultType,
} from "../requestResults/HttpErrorResult";
import { UnexpectedError } from "../requestResults/ErrorResult";
import type { RequestParams } from "./requestParams";
import type { SimpleRequestParams } from "./requestParams";
import { parse as parseLinkHeader } from "http-link-header";
export type CheckRootResult = boolean | CheckRootResultError;
@ -12,7 +12,7 @@ export type CheckRootResultError = HttpErrorResultType | UnexpectedError;
export async function checkRootContainer({
uri,
fetch,
}: Omit<RequestParams, "transaction">): Promise<CheckRootResult> {
}: SimpleRequestParams): Promise<CheckRootResult> {
try {
// Fetch options to determine the document type
const response = await fetch(uri, { method: "HEAD" });

@ -3,6 +3,7 @@ import {
getParentUri,
getSlug,
} from "../../util/rdfUtils";
import { isContainerUri } from "../../util/uriTypes";
import type { BinaryResult } from "../requestResults/BinaryResult";
import type { TurtleFormattingError } from "../requestResults/DataResult";
import { DataResult } from "../requestResults/DataResult";
@ -57,12 +58,17 @@ export async function createDataResource(
}
// Create the document
const parentUri = getParentUri(uri)!;
console.log("This is the URI", uri);
const headers: HeadersInit = {
"content-type": "text/turtle",
slug: getSlug(uri),
};
if (isContainerUri(uri)) {
headers.link = '<http://www.w3.org/ns/ldp#Container>; rel="type"';
}
const response = await fetch(parentUri, {
method: "post",
headers: {
"content-type": "text/turtle",
slug: getSlug(uri),
},
headers,
});
const httpError = HttpErrorResult.checkResponse(uri, response);

@ -0,0 +1,21 @@
import { universalAccess } from "@inrupt/solid-client";
import type {
AccessRuleFetchError,
AccessRuleResult,
} from "../requestResults/AccessRule";
import type { SimpleRequestParams } from "./requestParams";
export async function getAccessRules({
uri,
fetch,
}: SimpleRequestParams): Promise<AccessRuleResult | AccessRuleFetchError> {
throw new Error("Not Implemented");
// const [publicAccess, agentAccess] = await Promise.all([
// universalAccess.getPublicAccess(uri, { fetch }),
// universalAccess.getAgentAccessAll(uri, { fetch }),
// ]);
// if (agentAccess === null || publicAccess === null) {
// return new AccessRuleFetchError(uri);
// }
// return new AccessRuleResult(uri, publicAccess, agentAccess);
}

@ -6,3 +6,5 @@ export interface RequestParams {
fetch: typeof fetch;
transaction: TransactionalDataset<Quad>;
}
export type SimpleRequestParams = Omit<RequestParams, "transaction">;

@ -0,0 +1,80 @@
import type { AclDataset, WithChangeLog } from "@inrupt/solid-client";
import { getAgentAccessAll } from "@inrupt/solid-client";
import { getPublicAccess } from "@inrupt/solid-client";
import {
getSolidDatasetWithAcl,
hasResourceAcl,
hasFallbackAcl,
hasAccessibleAcl,
createAclFromFallbackAcl,
getResourceAcl,
setAgentResourceAccess,
saveAclFor,
setPublicDefaultAccess,
setPublicResourceAccess,
setAgentDefaultAccess,
} from "@inrupt/solid-client";
import { isContainerUri } from "../../util/uriTypes";
import type { AccessRule } from "../requestResults/AccessRule";
import { AccessRuleChangeResult } from "../requestResults/AccessRule";
import { AccessRuleFetchError } from "../requestResults/AccessRule";
import type { SimpleRequestParams } from "./requestParams";
export async function setAccessRules(
{ uri, fetch }: SimpleRequestParams,
newAccessRules: AccessRule,
): Promise<AccessRuleChangeResult | AccessRuleFetchError> {
console.warn("Access Control is stil underdeveloped. Use with caution.");
const isContainer = isContainerUri(uri);
// Code Copied from https://docs.inrupt.com/developer-tools/javascript/client-libraries/tutorial/manage-wac/
// Fetch the SolidDataset and its associated ACLs, if available:
const myDatasetWithAcl = await getSolidDatasetWithAcl(uri, { fetch });
// Obtain the SolidDataset's own ACL, if available,
// or initialise a new one, if possible:
let resourceAcl;
if (!hasResourceAcl(myDatasetWithAcl)) {
if (!hasAccessibleAcl(myDatasetWithAcl)) {
return new AccessRuleFetchError(
uri,
"The current user does not have permission to change access rights to this Resource.",
);
}
if (!hasFallbackAcl(myDatasetWithAcl)) {
return new AccessRuleFetchError(
"The current user does not have permission to see who currently has access to this Resource.",
);
}
resourceAcl = createAclFromFallbackAcl(myDatasetWithAcl);
} else {
resourceAcl = getResourceAcl(myDatasetWithAcl);
}
// Give someone Control access to the given Resource:
let updatedAcl: AclDataset & WithChangeLog = resourceAcl;
if (newAccessRules.public) {
if (isContainer) {
updatedAcl = setPublicDefaultAccess(updatedAcl, newAccessRules.public);
} else {
updatedAcl = setPublicResourceAccess(updatedAcl, newAccessRules.public);
}
}
if (newAccessRules.agent) {
const setAgentAccess = isContainer
? setAgentDefaultAccess
: setAgentResourceAccess;
Object.entries(newAccessRules.agent).forEach(([webId, rules]) => {
updatedAcl = setAgentAccess(updatedAcl, webId, rules);
});
}
// Now save the ACL:
await saveAclFor(myDatasetWithAcl, updatedAcl, { fetch });
return new AccessRuleChangeResult(
uri,
getPublicAccess(myDatasetWithAcl) || undefined,
getAgentAccessAll(myDatasetWithAcl) || undefined,
);
}

@ -44,13 +44,13 @@ export function uploadResource(
blob: Blob,
mimeType: string,
overwrite?: boolean,
): Promise<UploadResultWithoutOverwrite | UploadResult>;
): Promise<UploadResultWithoutOverwrite>;
export async function uploadResource(
params: RequestParams,
blob: Blob,
mimeType: string,
overwrite?: boolean,
): Promise<UploadResultWithoutOverwrite | UploadResult> {
): Promise<UploadResultWithoutOverwrite> {
const { uri, transaction, fetch } = params;
try {
if (overwrite) {

@ -1,9 +1,20 @@
import { namedNode } from "@rdfjs/data-model";
import { ContainerRequester } from "../requester/ContainerRequester";
import { UnexpectedError } from "../requester/requestResults/ErrorResult";
import {
AggregateError,
UnexpectedError,
} from "../requester/requestResults/ErrorResult";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type {
CreateResultErrors,
CreateResultWithoutOverwriteErrors,
} from "../requester/requests/createDataResource";
import type { DeleteResultError } from "../requester/requests/deleteResource";
import type { ReadResultError } from "../requester/requests/readResource";
import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import { getParentUri } from "../util/rdfUtils";
import type { ContainerUri } from "../util/uriTypes";
import { getParentUri, ldpContains } from "../util/rdfUtils";
import type { ContainerUri, LeafUri } from "../util/uriTypes";
import type { Leaf } from "./Leaf";
import { Resource } from "./Resource";
export class Container extends Resource {
@ -20,11 +31,9 @@ export class Container extends Resource {
return this.rootContainer;
}
getParentContainer(): Promise<Container | undefined> {
throw new Error("Method not implemented.");
}
async getRootContainer(): Promise<Container | CheckRootResultError> {
private async checkIfIsRootContainer(): Promise<
CheckRootResultError | undefined
> {
if (this.rootContainer === undefined) {
const rootContainerResult = await this.requester.isRootContainer();
if (typeof rootContainerResult !== "boolean") {
@ -32,6 +41,27 @@ export class Container extends Resource {
}
this.rootContainer = rootContainerResult;
}
}
async getParentContainer(): Promise<
Container | CheckRootResultError | undefined
> {
const checkResult = await this.checkIfIsRootContainer();
if (checkResult) return checkResult;
if (this.rootContainer) return undefined;
const parentUri = getParentUri(this.uri);
if (!parentUri) {
return new UnexpectedError(
this.uri,
new Error("Resource does not have a root container"),
);
}
return this.context.resourceStore.get(parentUri);
}
async getRootContainer(): Promise<Container | CheckRootResultError> {
const checkResult = await this.checkIfIsRootContainer();
if (checkResult) return checkResult;
if (this.rootContainer) {
return this;
}
@ -42,10 +72,86 @@ export class Container extends Resource {
new Error("Resource does not have a root container"),
);
}
return this.context.resourceStore.get(parentUri);
return this.context.resourceStore.get(parentUri).getRootContainer();
}
children(): (Leaf | Container)[] {
const childQuads = this.context.solidLdoDataset.match(
namedNode(this.uri),
ldpContains,
null,
namedNode(this.uri),
);
return childQuads.toArray().map((childQuad) => {
return this.context.resourceStore.get(childQuad.object.value);
});
}
createChildAndOverwrite(
slug: ContainerUri,
): Promise<Container | CreateResultErrors>;
createChildAndOverwrite(slug: LeafUri): Promise<Leaf | CreateResultErrors>;
createChildAndOverwrite(slug: string): Promise<Resource | CreateResultErrors>;
createChildAndOverwrite(
slug: string,
): Promise<Resource | CreateResultErrors> {
const resource = this.context.resourceStore.get(`${this.uri}${slug}`);
return resource.createAndOverwrite();
}
createChildIfAbsent(
slug: ContainerUri,
): Promise<Container | CreateResultWithoutOverwriteErrors>;
createChildIfAbsent(
slug: LeafUri,
): Promise<Leaf | CreateResultWithoutOverwriteErrors>;
createChildIfAbsent(
slug: string,
): Promise<Resource | CreateResultWithoutOverwriteErrors>;
createChildIfAbsent(
slug: string,
): Promise<Resource | CreateResultWithoutOverwriteErrors> {
const resource = this.context.resourceStore.get(`${this.uri}${slug}`);
return resource.createIfAbsent();
}
async clear(): Promise<
AggregateError<DeleteResultError | ReadResultError> | this
> {
const readResult = await this.read();
if (readResult.type === "error")
return new AggregateError(this.uri, [readResult]);
const errors = (
await Promise.all(
this.children().map(async (child) => {
const deleteError = await child.delete();
if (deleteError.type === "error") return deleteError;
}),
)
)
.flat()
.filter(
(
value,
): value is
| DeleteResultError
| AggregateError<DeleteResultError | ReadResultError> => !!value,
);
if (errors.length > 0) {
return new AggregateError(this.uri, errors);
}
return this;
}
getMimeType(): string {
throw new Error("Method not implemented.");
async delete(): Promise<
| this
| AggregateError<ReadResultError | DeleteResultError>
| DeleteResultError
> {
const clearResult = await this.clear();
if (clearResult.type === "error") return clearResult;
return this.parseResult(await this.requester.delete()) as
| this
| DeleteResultError;
}
}

@ -1,7 +1,12 @@
import type { DatasetChanges } from "@ldo/rdf-utils";
import { LeafRequester } from "../requester/LeafRequester";
import type { Requester } from "../requester/Requester";
import type { AbsentResult } from "../requester/requestResults/AbsentResult";
import type { BinaryResult } from "../requester/requestResults/BinaryResult";
import type { DataResult } from "../requester/requestResults/DataResult";
import type { ErrorResult } from "../requester/requestResults/ErrorResult";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type { DeleteResultError } from "../requester/requests/deleteResource";
import type { UpdateResultError } from "../requester/requests/updateDataResource";
import type {
UploadResultError,
@ -17,21 +22,38 @@ export class Leaf extends Resource {
protected requester: Requester;
readonly type = "leaf" as const;
protected binaryData: { data: Blob; mimeType: string } | undefined;
constructor(uri: LeafUri, context: SolidLdoDatasetContext) {
super(uri, context);
this.requester = new LeafRequester(uri, context);
}
getParentContainer(): Promise<Container | undefined> {
throw new Error("Method not implemented.");
protected parseResult<PossibleErrors extends ErrorResult>(
result: AbsentResult | BinaryResult | DataResult | PossibleErrors,
): this | PossibleErrors {
if (result.type === "binary") {
this.binaryData = {
data: result.blob,
mimeType: result.mimeType,
};
} else {
delete this.binaryData;
}
return super.parseResult(result);
}
getParentContainer(): Container {
const parentUri = getParentUri(this.uri)!;
return this.context.resourceStore.get(parentUri);
}
getRootContainer(): Promise<Container | CheckRootResultError> {
const parentUri = getParentUri(this.uri)!;
const parent = this.context.resourceStore.get(parentUri);
return parent.getRootContainer();
}
getMimeType(): string {
throw new Error("Method not implemented.");
getMimeType(): string | undefined {
return this.binaryData?.mimeType;
}
isBinary(): boolean | undefined {
if (!this.didInitialFetch) {
@ -50,21 +72,22 @@ export class Leaf extends Resource {
blob: Blob,
mimeType: string,
): Promise<this | UploadResultError> {
return this.parseResult(await this.requester.upload(blob, mimeType)) as
| this
| UploadResultError;
return this.parseResult(await this.requester.upload(blob, mimeType, true));
}
async uploadIfAbsent(
blob: Blob,
mimeType: string,
): Promise<this | UploadResultWithoutOverwriteError> {
return this.parseResult(
await this.requester.upload(blob, mimeType, true),
) as this | UploadResultWithoutOverwriteError;
return this.parseResult(await this.requester.upload(blob, mimeType));
}
update(_changes: DatasetChanges): Promise<this | UpdateResultError> {
throw new Error("Method not implemented");
}
// Delete Method
async delete(): Promise<this | DeleteResultError> {
return this.parseResult(await this.requester.delete());
}
}

@ -2,19 +2,23 @@ import type { SolidLdoDatasetContext } from "../SolidLdoDatasetContext";
import type { AbsentResult } from "../requester/requestResults/AbsentResult";
import type { BinaryResult } from "../requester/requestResults/BinaryResult";
import type { DataResult } from "../requester/requestResults/DataResult";
import {
UnexpectedError,
type ErrorResult,
} from "../requester/requestResults/ErrorResult";
import { type ErrorResult } from "../requester/requestResults/ErrorResult";
import type {
CreateResultErrors,
CreateResultWithoutOverwriteErrors,
} from "../requester/requests/createDataResource";
import type { DeleteResultError } from "../requester/requests/deleteResource";
import type { ReadResultError } from "../requester/requests/readResource";
import type { Container } from "./Container";
import type { Requester } from "../requester/Requester";
import type { CheckRootResultError } from "../requester/requests/checkRootContainer";
import type {
AccessRule,
AccessRuleChangeResult,
AccessRuleFetchError,
AccessRuleResult,
} from "../requester/requestResults/AccessRule";
import { getAccessRules } from "../requester/requests/getAccessRules";
import { setAccessRules } from "../requester/requests/setAccessRules";
export abstract class Resource {
// All intance variables
@ -24,7 +28,6 @@ export abstract class Resource {
protected abstract readonly requester: Requester;
protected didInitialFetch: boolean = false;
protected absent: boolean | undefined;
protected binaryData: { data: Blob; mimeType: string } | undefined;
constructor(uri: string, context: SolidLdoDatasetContext) {
this.uri = uri;
@ -68,43 +71,26 @@ export abstract class Resource {
return this.absent === undefined ? undefined : !this.absent;
}
protected parseResult(
result: AbsentResult | BinaryResult | DataResult | ErrorResult,
) {
protected parseResult<PossibleErrors extends ErrorResult>(
result: AbsentResult | BinaryResult | DataResult | PossibleErrors,
): this | PossibleErrors {
switch (result.type) {
case "error":
return result;
case "absent":
this.didInitialFetch = true;
this.absent = true;
delete this.binaryData;
return this;
case "data":
this.didInitialFetch = true;
this.absent = false;
delete this.binaryData;
return this;
case "binary":
default:
this.didInitialFetch = true;
this.absent = false;
this.binaryData = {
data: result.blob,
mimeType: result.mimeType,
};
return this;
default:
return new UnexpectedError(
this.uri,
new Error("Unknown request result"),
);
}
}
// Read Methods
async read(): Promise<this | ReadResultError> {
return this.parseResult(await this.requester.read()) as
| this
| ReadResultError;
return this.parseResult(await this.requester.read());
}
async readIfUnfetched(): Promise<this | ReadResultError> {
if (this.didInitialFetch) {
@ -115,30 +101,26 @@ export abstract class Resource {
// Create Methods
async createAndOverwrite(): Promise<this | CreateResultErrors> {
return this.parseResult(await this.requester.createDataResource(true)) as
| this
| CreateResultErrors;
return this.parseResult(await this.requester.createDataResource(true));
}
async createIfAbsent(): Promise<this | CreateResultWithoutOverwriteErrors> {
return this.parseResult(await this.requester.createDataResource()) as
| this
| CreateResultWithoutOverwriteErrors;
}
// Delete Method
async delete(): Promise<Resource | DeleteResultError> {
return this.parseResult(await this.requester.delete()) as
| Resource
| DeleteResultError;
return this.parseResult(await this.requester.createDataResource());
}
// Parent Container Methods -- Remember to change for Container
abstract getParentContainer(): Promise<Container | undefined>;
abstract getRootContainer(): Promise<Container | CheckRootResultError>;
// Exclusing Methods =========================================================
// Data Methods (Data Leaf Only)
// Binary Methods (Binary Only)
abstract getMimeType(): string;
async getAccessRules(): Promise<AccessRuleResult | AccessRuleFetchError> {
return getAccessRules({ uri: this.uri, fetch: this.context.fetch });
}
async setAccessRules(
newAccessRules: AccessRule,
): Promise<AccessRuleChangeResult | AccessRuleFetchError> {
return setAccessRules(
{ uri: this.uri, fetch: this.context.fetch },
newAccessRules,
);
}
}

@ -6,11 +6,15 @@ import type { Dataset } from "@rdfjs/types";
import type { ContainerUri } from "./uriTypes";
import { isContainerUri } from "./uriTypes";
const ldpContains = namedNode("http://www.w3.org/ns/ldp#contains");
const rdfType = namedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
const ldpResource = namedNode("http://www.w3.org/ns/ldp#Resource");
const ldpContainer = namedNode("http://www.w3.org/ns/ldp#Container");
const ldpBasicContainer = namedNode("http://www.w3.org/ns/ldp#BasicContainer");
export const ldpContains = namedNode("http://www.w3.org/ns/ldp#contains");
export const rdfType = namedNode(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type",
);
export const ldpResource = namedNode("http://www.w3.org/ns/ldp#Resource");
export const ldpContainer = namedNode("http://www.w3.org/ns/ldp#Container");
export const ldpBasicContainer = namedNode(
"http://www.w3.org/ns/ldp#BasicContainer",
);
export function getParentUri(uri: string): ContainerUri | undefined {
const urlObject = new URL(uri);
@ -32,7 +36,7 @@ export function getParentUri(uri: string): ContainerUri | undefined {
export function getSlug(uri: string): string {
const urlObject = new URL(uri);
const pathItems = urlObject.pathname.split("/");
return pathItems[pathItems.length - 1];
return pathItems[pathItems.length - 1] || pathItems[pathItems.length - 2];
}
export function deleteResourceRdfFromContainer(

Loading…
Cancel
Save