fix(fs): better errors for unresolved selectors

Adds an appropriate error at a couple different levels. The first is in
FilesystemService. Callers of FilesystemService.node are expected to
provide either an absolute path or an instance of a filesystem node
selector, but there wasn't an explicit check for this so instead an
arbitrary error (read property of undefined) would be thrown. The second
is a higher-level APIError in the batch executor that's a bit more
helpful than a 500 Internal Server Error.
This commit is contained in:
KernelDeimos
2025-10-08 16:03:21 -04:00
committed by Eric Dubé
parent d6a168e402
commit 7cc79077d1
4 changed files with 45 additions and 7 deletions
+5
View File
@@ -283,6 +283,11 @@ module.exports = class APIError {
status: 400,
message: 'Invalid file metadata.',
},
'unresolved_relative_path': {
status: 400,
message: ({ path }) => `Unresolved relative path: ${quot(path)}. ` +
"You may need to specify a full path starting with '/'.",
},
// Open
'no_suitable_app': {
@@ -19,7 +19,7 @@
// TODO: database access can be a service
const { RESOURCE_STATUS_PENDING_CREATE } = require('../modules/puterfs/ResourceService.js');
const { TraceService } = require('../services/TraceService.js');
const { NodePathSelector, NodeUIDSelector, NodeInternalIDSelector } = require('./node/selectors.js');
const { NodePathSelector, NodeUIDSelector, NodeInternalIDSelector, NodeSelector } = require('./node/selectors.js');
const FSNodeContext = require('./FSNodeContext.js');
const { Context } = require('../util/context.js');
const APIError = require('../api/APIError.js');
@@ -29,6 +29,8 @@ const { UserActorType } = require('../services/auth/Actor');
const { get_user } = require('../helpers');
const BaseService = require('../services/BaseService');
const { MANAGE_PERM_PREFIX } = require('../services/auth/permissionConts.mjs');
const { PuterFSProvider } = require('../modules/puterfs/lib/PuterFSProvider.js');
const { quot } = require('@heyputer/putility/src/libs/string.js');
class FilesystemService extends BaseService {
static MODULES = {
@@ -329,6 +331,14 @@ class FilesystemService extends BaseService {
selector = new NodeInternalIDSelector('mysql', selector.mysql_id);
}
}
if ( ! (selector instanceof NodeSelector) ) {
throw new Error(
'FileSystemService could not resolve the specified node value ' +
quot(''+selector) + ` (type: ${typeof selector}) ` +
'to a filesystem node selector',
);
}
system_dir_check: {
if ( ! (selector instanceof NodePathSelector) ) break system_dir_check;
+26 -6
View File
@@ -19,8 +19,22 @@
const _path = require('path');
const { PuterPath } = require('../lib/PuterPath');
class NodePathSelector {
/**
* The base class doesn't add any functionality, but it's useful for
* `instanceof` checks.
*/
class NodeSelector {
constructor () {
if ( this.constructor === NodeSelector ) {
throw new Error('cannot instantiate NodeSelector directly; ' +
'that would be like using this: https://devmeme.puter.site/plug.webp');
}
}
}
class NodePathSelector extends NodeSelector {
constructor (path) {
super();
this.value = path;
}
@@ -34,8 +48,9 @@ class NodePathSelector {
}
}
class NodeUIDSelector {
class NodeUIDSelector extends NodeSelector {
constructor (uid) {
super();
this.value = uid;
}
@@ -58,8 +73,9 @@ class NodeUIDSelector {
}
}
class NodeInternalIDSelector {
class NodeInternalIDSelector extends NodeSelector {
constructor (service, id, debugInfo) {
super();
this.service = service;
this.id = id;
this.debugInfo = debugInfo;
@@ -81,8 +97,9 @@ class NodeInternalIDSelector {
}
}
class NodeChildSelector {
class NodeChildSelector extends NodeSelector {
constructor (parent, name) {
super();
this.parent = parent;
this.name = name;
}
@@ -101,7 +118,7 @@ class NodeChildSelector {
}
}
class RootNodeSelector {
class RootNodeSelector extends NodeSelector {
static entry = {
is_dir: true,
is_root: true,
@@ -114,6 +131,7 @@ class RootNodeSelector {
node.uid = PuterPath.NULL_UUID;
}
constructor () {
super();
this.entry = this.constructor.entry;
}
@@ -122,8 +140,9 @@ class RootNodeSelector {
}
}
class NodeRawEntrySelector {
class NodeRawEntrySelector extends NodeSelector {
constructor (entry) {
super();
// Fix entries from get_descendants
if ( ! entry.uuid && entry.uid ) {
entry.uuid = entry.uid;
@@ -190,6 +209,7 @@ const relativeSelector = (parent, path) => {
}
module.exports = {
NodeSelector,
NodePathSelector,
NodeUIDSelector,
NodeInternalIDSelector,
@@ -96,6 +96,9 @@ module.exports = class PathResolver {
if ( inputPath === '~' ) {
return `/${username}`;
}
if ( inputPath.startsWith('.') ) {
throw APIError.create('unresolved_relative_path', null, { path: inputPath });
}
const refName = this.getReferenceUsed(inputPath);
if ( refName === null ) return inputPath;