Skip to content

Commit

Permalink
fix(manager, slice-machine-ui, playwright): environment edge cases (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore authored Dec 27, 2023
1 parent 99f5aa6 commit 9e4c619
Show file tree
Hide file tree
Showing 22 changed files with 449 additions and 234 deletions.
1 change: 1 addition & 0 deletions packages/manager/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export {
isUnauthenticatedError,
isUnauthorizedError,
isUnexpectedDataError,
isInvalidActiveEnvironmentError,
} from "../errors";

export { DecodeError } from "../lib/DecodeError";
92 changes: 58 additions & 34 deletions packages/manager/src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
import { HookError } from "@slicemachine/plugin-kit";

export class SliceMachineError extends Error {
_sliceMachineError = true;
name = "SliceMachineError";
name = "SMSliceMachineError";
}
export class UnauthorizedError extends SliceMachineError {
name = "UnauthorizedError";
name = "SMUnauthorizedError" as const;
}
export class UnauthenticatedError extends SliceMachineError {
name = "UnauthenticatedError";
name = "SMUnauthenticatedError" as const;
message = "Authenticate before trying again.";
}
export class NotFoundError extends SliceMachineError {
name = "NotFoundError";
name = "SMNotFoundError" as const;
}
export class UnexpectedDataError extends SliceMachineError {
name = "UnexpectedDataError";
name = "SMUnexpectedDataError" as const;
}
export class InternalError extends SliceMachineError {
name = "InternalError";
name = "SMInternalError" as const;
}
export class PluginError extends SliceMachineError {
name = "PluginError";
name = "SMPluginError" as const;
}
export class PluginHookResultError extends SliceMachineError {
name = "PluginHookResultError";
name = "SMPluginHookResultError" as const;

constructor(errors: HookError[]) {
super(
Expand All @@ -37,49 +36,74 @@ export class PluginHookResultError extends SliceMachineError {
);
}
}
export class InvalidActiveEnvironmentError extends SliceMachineError {
name = "SMInvalidActiveEnvironmentError" as const;
}

type SliceMachineErrorNames =
| "SMSliceMachineError"
| UnauthorizedError["name"]
| UnauthenticatedError["name"]
| NotFoundError["name"]
| UnexpectedDataError["name"]
| InternalError["name"]
| PluginError["name"]
| PluginHookResultError["name"]
| InvalidActiveEnvironmentError["name"];

type ShallowSliceMachineError<TName extends SliceMachineErrorNames> = Error & {
name: TName;
};

export const isSliceMachineError = (
export const isSliceMachineError = <TName extends SliceMachineErrorNames>(
error: unknown,
): error is SliceMachineError => {
// TODO: Discuss a stronger way to serialize error for the client to detect with r19
// @ts-expect-error We don't want to add "dom" to tsconfig "lib" because of the TODO
if (typeof window !== "undefined") {
return typeof error === "object" && error !== null;
} else {
return (
typeof error === "object" &&
error !== null &&
"_sliceMachineError" in error
);
}
name?: TName,
): error is TName extends string ? ShallowSliceMachineError<TName> : Error => {
const isErrorInstance = error instanceof Error;

return name === undefined
? isErrorInstance && error.name.startsWith("SM")
: isErrorInstance && error.name === name;
};

export const isUnauthorizedError = (
error: unknown,
): error is UnauthorizedError => {
return isSliceMachineError(error) && error.name === UnauthorizedError.name;
): error is ShallowSliceMachineError<"SMUnauthorizedError"> => {
return isSliceMachineError(error, "SMUnauthorizedError");
};

export const isUnauthenticatedError = (
error: unknown,
): error is UnauthenticatedError => {
return isSliceMachineError(error) && error.name === UnauthenticatedError.name;
): error is ShallowSliceMachineError<"SMUnauthenticatedError"> => {
return isSliceMachineError(error, "SMUnauthenticatedError");
};

export const isNotFoundError = (error: unknown): error is NotFoundError => {
return isSliceMachineError(error) && error.name === NotFoundError.name;
export const isNotFoundError = (
error: unknown,
): error is ShallowSliceMachineError<"SMNotFoundError"> => {
return isSliceMachineError(error, "SMNotFoundError");
};

export const isUnexpectedDataError = (
error: unknown,
): error is UnexpectedDataError => {
return isSliceMachineError(error) && error.name === UnexpectedDataError.name;
): error is ShallowSliceMachineError<"SMUnexpectedDataError"> => {
return isSliceMachineError(error, "SMUnexpectedDataError");
};

export const isInternalError = (
error: unknown,
): error is ShallowSliceMachineError<"SMInternalError"> => {
return isSliceMachineError(error, "SMInternalError");
};

export const isInternalError = (error: unknown): error is InternalError => {
return isSliceMachineError(error) && error.name === InternalError.name;
export const isPluginError = (
error: unknown,
): error is ShallowSliceMachineError<"SMPluginError"> => {
return isSliceMachineError(error, "SMPluginError");
};

export const isPluginError = (error: unknown): error is PluginError => {
return isSliceMachineError(error) && error.name === PluginError.name;
export const isInvalidActiveEnvironmentError = (
error: unknown,
): error is ShallowSliceMachineError<"SMInvalidActiveEnvironmentError"> => {
return isSliceMachineError(error, "SMInvalidActiveEnvironmentError");
};
1 change: 1 addition & 0 deletions packages/manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export {
InternalError,
PluginError,
PluginHookResultError,
InvalidActiveEnvironmentError,
} from "./errors";

export { getEnvironmentInfo } from "./getEnvironmentInfo";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
UnauthenticatedError,
UnauthorizedError,
UnexpectedDataError,
isUnauthenticatedError,
} from "../../errors";

import { BaseManager } from "../BaseManager";
Expand Down Expand Up @@ -67,12 +66,19 @@ type PrismicRepositoryManagerPushDocumentsArgs = {
};

type PrismicRepositoryManagerFetchEnvironmentsArgs = {
includeAllPersonalEnvironments?: boolean;
/**
* If set to `true`, all environments are returned regardless of the user's
* permission level.
*
* If set to `false`, only environments the user can access are returned.
*
* @defaultValue `false`
*/
includeAll?: boolean;
};

type PrismicRepositoryManagerFetchEnvironmentsReturnType = {
environments?: Environment[];
error?: unknown;
};

export class PrismicRepositoryManager extends BaseManager {
Expand Down Expand Up @@ -451,20 +457,7 @@ export class PrismicRepositoryManager extends BaseManager {
const url = new URL(`./environments`, API_ENDPOINTS.SliceMachineV1);
url.searchParams.set("repository", repositoryName);

let res;
try {
res = await this._fetch({ url });
} catch (error) {
if (isUnauthenticatedError(error)) {
return { error };
}

return {
error: new UnexpectedDataError(
"Unexpected Error while fetching Environments",
),
};
}
const res = await this._fetch({ url });

if (res.ok) {
const json = await res.json();
Expand All @@ -482,44 +475,20 @@ export class PrismicRepositoryManager extends BaseManager {
);

if (error) {
return {
error: new UnexpectedDataError(
`Failed to decode environments: ${error.errors.join(", ")}`,
),
};
throw new UnexpectedDataError(
`Failed to decode environments: ${error.errors.join(", ")}`,
);
}

if ("results" in value) {
let environments = value.results;

// Only include the user's personal environment
// by default. We must filter in the manager
// because the API returns all personal
// environments.
if (!args?.includeAllPersonalEnvironments) {
try {
const profile = await this.user.getProfile();

environments = environments.filter((environment) => {
if (environment.kind === "dev") {
return environment.users.some(
(user) => user.id === profile.shortId,
);
}

return true;
});
} catch (e) {
if (isUnauthenticatedError(error)) {
return { error };
}
if (!args?.includeAll) {
const profile = await this.user.getProfile();

return {
error: new UnexpectedDataError(
"Unexpected Error while fetching Environments",
),
};
}
environments = environments.filter((environment) =>
environment.users.some((user) => user.id === profile.shortId),
);
}

return { environments: sortEnvironments(environments) };
Expand All @@ -529,11 +498,11 @@ export class PrismicRepositoryManager extends BaseManager {
switch (res.status) {
case 400:
case 401:
return { error: new UnauthenticatedError() };
throw new UnauthenticatedError();
case 403:
return { error: new UnauthorizedError() };
throw new UnauthorizedError();
default:
return { error: new Error("Failed to fetch environments.") };
throw new Error("Failed to fetch environments.");
}
}

Expand Down
8 changes: 2 additions & 6 deletions packages/manager/src/managers/project/ProjectManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
SliceMachineError,
InternalError,
PluginError,
UnexpectedDataError,
InvalidActiveEnvironmentError,
} from "../../errors";

import { SLICE_MACHINE_CONFIG_FILENAME } from "../../constants/SLICE_MACHINE_CONFIG_FILENAME";
Expand Down Expand Up @@ -472,11 +472,7 @@ export class ProjectManager extends BaseManager {
);

if (!activeEnvironment) {
throw new UnexpectedDataError(
`The active environment (${
activeEnvironmentDomain ?? "Production"
}) does not match one of the repository's environments.`,
);
throw new InvalidActiveEnvironmentError();
}

return { activeEnvironment };
Expand Down
Loading

0 comments on commit 9e4c619

Please sign in to comment.