Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions apps/server/src/assets/AssetAccess.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ describe("AssetAccess", () => {
workspaceRoot: root,
}).pipe(Effect.flip);
expect(error.message).toBe("Workspace file path must be relative to the project root.");
expect(error).toMatchObject({
operation: "validate-workspace-path",
resource: {
_tag: "workspace-file",
threadId: "thread-1",
path: htmlPath,
},
});
expect(error.cause).toBeInstanceOf(WorkspacePaths.WorkspacePathOutsideRootError);
}).pipe(Effect.provide(testLayer)),
);
Expand Down Expand Up @@ -121,6 +129,14 @@ describe("AssetAccess", () => {
}).pipe(Effect.provideService(FileSystem.FileSystem, failingFileSystem), Effect.flip);

expect(error.message).toBe("Failed to inspect the workspace asset.");
expect(error).toMatchObject({
operation: "inspect-workspace-asset",
resource: {
_tag: "workspace-file",
threadId: "thread-1",
path: htmlPath,
},
});
expect(error.cause).toBe(cause);
}).pipe(Effect.provide(testLayer)),
);
Expand Down
44 changes: 40 additions & 4 deletions apps/server/src/assets/AssetAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,18 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
switch (input.resource._tag) {
case "workspace-file": {
if (!input.workspaceRoot) {
return yield* new AssetAccessError({ message: "Workspace context was not found." });
return yield* new AssetAccessError({
operation: "resolve-workspace-context",
resource: input.resource,
message: "Workspace context was not found.",
});
}
const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(input.workspaceRoot).pipe(
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "normalize-workspace-root",
resource: input.resource,
message: "Failed to normalize the workspace root.",
cause,
}),
Expand All @@ -185,13 +191,17 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "validate-workspace-path",
resource: input.resource,
message: "Workspace file path must be relative to the project root.",
cause,
}),
),
);
if (!isWorkspacePreviewEntryPath(resolved.relativePath)) {
return yield* new AssetAccessError({
operation: "validate-preview-type",
resource: input.resource,
message: "Only browser documents and images can be previewed.",
});
}
Expand All @@ -202,18 +212,26 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "inspect-workspace-asset",
resource: input.resource,
message: "Failed to inspect the workspace asset.",
cause,
}),
),
);
if (!canonicalFile) {
return yield* new AssetAccessError({ message: "Workspace asset was not found." });
return yield* new AssetAccessError({
operation: "locate-workspace-asset",
resource: input.resource,
message: "Workspace asset was not found.",
});
}
const canonicalWorkspaceRoot = yield* fileSystem.realPath(workspaceRoot).pipe(
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "resolve-workspace",
resource: input.resource,
message: "Failed to resolve workspace.",
cause,
}),
Expand Down Expand Up @@ -244,7 +262,11 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
attachmentId: input.resource.attachmentId,
});
if (!attachmentPath) {
return yield* new AssetAccessError({ message: "Attachment was not found." });
return yield* new AssetAccessError({
operation: "locate-attachment",
resource: input.resource,
message: "Attachment was not found.",
});
}
claims = {
version: 1,
Expand All @@ -260,6 +282,8 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "normalize-workspace-root",
resource: input.resource,
message: "Failed to normalize the workspace root.",
cause,
}),
Expand All @@ -270,6 +294,8 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "resolve-project-favicon",
resource: input.resource,
message: "Failed to resolve project favicon.",
cause,
}),
Expand All @@ -282,13 +308,19 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "inspect-project-favicon",
resource: input.resource,
message: "Failed to inspect the project favicon.",
cause,
}),
),
))
) {
return yield* new AssetAccessError({ message: "Project favicon was not found." });
return yield* new AssetAccessError({
operation: "locate-project-favicon",
resource: input.resource,
message: "Project favicon was not found.",
});
}
claims = {
version: 1,
Expand All @@ -297,6 +329,8 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "resolve-workspace",
resource: input.resource,
message: "Failed to resolve workspace.",
cause,
}),
Expand All @@ -315,6 +349,8 @@ export const issueAssetUrl = Effect.fn("AssetAccess.issueAssetUrl")(function* (i
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "load-signing-key",
resource: input.resource,
message: "Failed to load the asset signing key.",
cause,
}),
Expand Down
8 changes: 8 additions & 0 deletions apps/server/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1341,13 +1341,17 @@ const makeWsRpcLayer = (currentSession: EnvironmentAuth.AuthenticatedSession) =>
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "resolve-workspace-context",
resource: input.resource,
message: "Failed to resolve workspace context.",
cause,
}),
),
);
if (Option.isNone(thread)) {
return yield* new AssetAccessError({
operation: "resolve-workspace-context",
resource: input.resource,
message: "Workspace context was not found.",
});
}
Expand All @@ -1357,13 +1361,17 @@ const makeWsRpcLayer = (currentSession: EnvironmentAuth.AuthenticatedSession) =>
Effect.mapError(
(cause) =>
new AssetAccessError({
operation: "resolve-workspace-context",
resource: input.resource,
message: "Failed to resolve workspace context.",
cause,
}),
),
);
if (Option.isNone(project)) {
return yield* new AssetAccessError({
operation: "resolve-workspace-context",
resource: input.resource,
message: "Workspace context was not found.",
});
}
Expand Down
18 changes: 18 additions & 0 deletions packages/contracts/src/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,27 @@ export const AssetCreateUrlResult = Schema.Struct({
});
export type AssetCreateUrlResult = typeof AssetCreateUrlResult.Type;

export const AssetAccessOperation = Schema.Literals([
"resolve-workspace-context",
"normalize-workspace-root",
"validate-workspace-path",
"validate-preview-type",
"inspect-workspace-asset",
"locate-workspace-asset",
"resolve-workspace",
"locate-attachment",
"resolve-project-favicon",
"inspect-project-favicon",
"locate-project-favicon",
"load-signing-key",
]);
export type AssetAccessOperation = typeof AssetAccessOperation.Type;

export class AssetAccessError extends Schema.TaggedErrorClass<AssetAccessError>()(
"AssetAccessError",
{
operation: AssetAccessOperation,
resource: AssetResource,
message: TrimmedNonEmptyString,
cause: Schema.optional(Schema.Defect()),
},
Expand Down
Loading