diff --git a/packages/solid/src/requester/requests/checkRootContainer.ts b/packages/solid/src/requester/requests/checkRootContainer.ts index 18f4988..394b3d5 100644 --- a/packages/solid/src/requester/requests/checkRootContainer.ts +++ b/packages/solid/src/requester/requests/checkRootContainer.ts @@ -1,6 +1,5 @@ import type { BasicRequestOptions } from "./requestOptions"; import { parse as parseLinkHeader } from "http-link-header"; -import { NoncompliantPodError } from "../results/error/NoncompliantPodError"; import type { CheckRootContainerSuccess } from "../results/success/CheckRootContainerSuccess"; import type { HttpErrorResultType, @@ -21,7 +20,6 @@ export type CheckRootResult = CheckRootContainerSuccess | CheckRootResultError; */ export type CheckRootResultError = | HttpErrorResultType - | NoncompliantPodError | UnexpectedHttpError | UnexpectedResourceError; @@ -37,10 +35,15 @@ export type CheckRootResultError = export function checkHeadersForRootContainer( uri: ContainerUri, headers: Headers, -): CheckRootContainerSuccess | NoncompliantPodError { +): CheckRootContainerSuccess { const linkHeader = headers.get("link"); if (!linkHeader) { - return new NoncompliantPodError(uri, "No link header present in request."); + return { + uri, + isRootContainer: false, + type: "checkRootContainerSuccess", + isError: false, + }; } const parsedLinkHeader = parseLinkHeader(linkHeader); const types = parsedLinkHeader.get("rel", "type"); diff --git a/packages/solid/src/requester/requests/readResource.ts b/packages/solid/src/requester/requests/readResource.ts index ec2c568..a0a961f 100644 --- a/packages/solid/src/requester/requests/readResource.ts +++ b/packages/solid/src/requester/requests/readResource.ts @@ -139,7 +139,6 @@ export async function readResource( } if (isContainerUri(uri)) { const result = checkHeadersForRootContainer(uri, response.headers); - if (result.isError) return result; return { isError: false, type: "containerReadSuccess", diff --git a/packages/solid/src/requester/results/error/NoRootContainerError.ts b/packages/solid/src/requester/results/error/NoRootContainerError.ts new file mode 100644 index 0000000..8a17f2f --- /dev/null +++ b/packages/solid/src/requester/results/error/NoRootContainerError.ts @@ -0,0 +1,17 @@ +import { ResourceError } from "./ErrorResult"; + +/** + * A NoncompliantPodError is returned when the server responded in a way that is + * not compliant with the Solid specification. + */ +export class NoRootContainerError extends ResourceError { + readonly type = "noRootContainerError" as const; + + /** + * @param uri - the URI of the requested resource + * @param message - a custom message for the error + */ + constructor(uri: string) { + super(uri, `${uri} has not root container.`); + } +} diff --git a/packages/solid/src/resource/Container.ts b/packages/solid/src/resource/Container.ts index e21fcde..2670e28 100644 --- a/packages/solid/src/resource/Container.ts +++ b/packages/solid/src/resource/Container.ts @@ -19,7 +19,6 @@ import type { ReadResultError, } from "../requester/requests/readResource"; import { AggregateError } from "../requester/results/error/ErrorResult"; -import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError"; import type { DeleteSuccess } from "../requester/results/success/DeleteSuccess"; import type { AbsentReadSuccess } from "../requester/results/success/ReadSuccess"; import type { ContainerReadSuccess } from "../requester/results/success/ReadSuccess"; @@ -31,6 +30,7 @@ import type { Leaf } from "./Leaf"; import type { SharedStatuses } from "./Resource"; import { Resource } from "./Resource"; import type { ResourceResult } from "./resourceResult/ResourceResult"; +import { NoRootContainerError } from "../requester/results/error/NoRootContainerError"; /** * Represents the current status of a specific container on a Pod as known by @@ -222,7 +222,8 @@ export class Container extends Resource { /** * Gets the root container of this container. If this container is the root * container, this function returns itself. - * @returns The root container for this container + * @returns The root container for this container or undefined if there is no + * root container. * * @example * Suppose the root container is at `https://example.com/` @@ -237,11 +238,13 @@ export class Container extends Resource { * } * ``` */ - async getRootContainer(): Promise { + async getRootContainer(): Promise< + Container | CheckRootResultError | NoRootContainerError + > { const parentContainerResult = await this.getParentContainer(); if (parentContainerResult?.isError) return parentContainerResult; if (!parentContainerResult) { - return this; + return this.isRootContainer() ? this : new NoRootContainerError(this.uri); } return parentContainerResult.getRootContainer(); } @@ -277,10 +280,7 @@ export class Container extends Resource { if (this.rootContainer) return undefined; const parentUri = getParentUri(this.uri); if (!parentUri) { - return new NoncompliantPodError( - this.uri, - `${this.uri} is not root does not have a parent container`, - ); + return undefined; } return this.context.resourceStore.get(parentUri); } diff --git a/packages/solid/src/resource/Leaf.ts b/packages/solid/src/resource/Leaf.ts index 60fba98..d735c55 100644 --- a/packages/solid/src/resource/Leaf.ts +++ b/packages/solid/src/resource/Leaf.ts @@ -23,6 +23,7 @@ import type { Container } from "./Container"; import type { SharedStatuses } from "./Resource"; import { Resource } from "./Resource"; import type { ResourceResult } from "./resourceResult/ResourceResult"; +import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError"; /** * Represents the current status of a specific Leaf on a Pod as known by LDO. @@ -338,7 +339,9 @@ export class Leaf extends Resource { * } * ``` */ - async getRootContainer(): Promise { + async getRootContainer(): Promise< + Container | CheckRootResultError | NoRootContainerError + > { const parent = await this.getParentContainer(); return parent.getRootContainer(); } diff --git a/packages/solid/src/resource/Resource.ts b/packages/solid/src/resource/Resource.ts index 41bcb51..e52d8d1 100644 --- a/packages/solid/src/resource/Resource.ts +++ b/packages/solid/src/resource/Resource.ts @@ -32,6 +32,7 @@ import { getWacRuleWithAclUri, type GetWacRuleResult } from "./wac/getWacRule"; import { NoncompliantPodError } from "../requester/results/error/NoncompliantPodError"; import { setWacRuleForAclUri, type SetWacRuleResult } from "./wac/setWacRule"; import type { LeafUri } from "../util/uriTypes"; +import type { NoRootContainerError } from "../requester/results/error/NoRootContainerError"; /** * Statuses shared between both Leaf and Container @@ -524,7 +525,9 @@ export abstract class Resource extends (EventEmitter as new () => TypedEmitter<{ * } * ``` */ - abstract getRootContainer(): Promise; + abstract getRootContainer(): Promise< + Container | CheckRootResultError | NoRootContainerError + >; abstract getParentContainer(): Promise< Container | CheckRootResultError | undefined diff --git a/packages/solid/test/Integration.test.ts b/packages/solid/test/Integration.test.ts index 127acbd..deba138 100644 --- a/packages/solid/test/Integration.test.ts +++ b/packages/solid/test/Integration.test.ts @@ -417,7 +417,7 @@ describe("Integration", () => { expect(result.message).toBe("Something happened."); }); - it("Returns an error if there is no link header for a container request", async () => { + it("Does not return an error if there is no link header for a container request", async () => { fetchMock.mockResolvedValueOnce( new Response(TEST_CONTAINER_TTL, { status: 200, @@ -430,12 +430,9 @@ describe("Integration", () => { isReading: true, isDoingInitialFetch: true, }); - expect(result.isError).toBe(true); - if (!result.isError) return; - expect(result.type).toBe("noncompliantPodError"); - expect(result.message).toMatch( - /\Response from .* is not compliant with the Solid Specification: No link header present in request\./, - ); + expect(result.isError).toBe(false); + if (result.isError) return; + expect(result.resource.isRootContainer()).toBe(false); }); it("knows nothing about a leaf resource if it is not fetched", () => { @@ -577,7 +574,19 @@ describe("Integration", () => { expect(result.isRootContainer()).toBe(true); }); - it("Returns an error if there is no link header for a container request", async () => { + it("Returns an error if there is no root container", async () => { + fetchMock.mockResolvedValueOnce( + new Response(TEST_CONTAINER_TTL, { + status: 200, + headers: new Headers({ "content-type": "text/turtle" }), + }), + ); + fetchMock.mockResolvedValueOnce( + new Response(TEST_CONTAINER_TTL, { + status: 200, + headers: new Headers({ "content-type": "text/turtle" }), + }), + ); fetchMock.mockResolvedValueOnce( new Response(TEST_CONTAINER_TTL, { status: 200, @@ -588,10 +597,8 @@ describe("Integration", () => { const result = await resource.getRootContainer(); expect(result.isError).toBe(true); if (!result.isError) return; - expect(result.type).toBe("noncompliantPodError"); - expect(result.message).toMatch( - /\Response from .* is not compliant with the Solid Specification: No link header present in request\./, - ); + expect(result.type).toBe("noRootContainerError"); + expect(result.message).toMatch(/\.* has not root container\./); }); it("An error to be returned if a common http error is encountered", async () => { @@ -629,7 +636,7 @@ describe("Integration", () => { const resource = solidLdoDataset.getResource(ROOT_CONTAINER); const result = await resource.getRootContainer(); expect(result.isError).toBe(true); - expect(result.type).toBe("noncompliantPodError"); + expect(result.type).toBe("noRootContainerError"); }); });