mirror of
https://github.com/HeyPuter/puter.git
synced 2025-02-02 23:28:39 +08:00
refactor: allow service-specific API errors to be registered
This commit is contained in:
parent
8557c54527
commit
ff62d9009e
@ -324,36 +324,6 @@ module.exports = class APIError {
|
||||
message: () => 'Invalid token.',
|
||||
},
|
||||
|
||||
// drivers
|
||||
'interface_not_found': {
|
||||
status: 404,
|
||||
message: ({ interface_name }) => `Interface not found: ${quot(interface_name)}`,
|
||||
},
|
||||
'no_implementation_available': {
|
||||
status: 502,
|
||||
message: ({
|
||||
iface,
|
||||
interface_name,
|
||||
driver
|
||||
}) => `No implementation available for ` +
|
||||
(iface ?? interface_name) ? 'interface' : 'driver' +
|
||||
' ' + quot(iface ?? interface_name ?? driver) + '.',
|
||||
},
|
||||
'method_not_found': {
|
||||
status: 404,
|
||||
message: ({ interface_name, method_name }) => `Method not found: ${quot(method_name)} on interface ${quot(interface_name)}`,
|
||||
},
|
||||
'missing_required_argument': {
|
||||
status: 400,
|
||||
message: ({ interface_name, method_name, arg_name }) =>
|
||||
`Missing required argument ${quot(arg_name)} for method ${quot(method_name)} on interface ${quot(interface_name)}`,
|
||||
},
|
||||
'argument_consolidation_failed': {
|
||||
status: 400,
|
||||
message: ({ interface_name, method_name, arg_name, message }) =>
|
||||
`Failed to parse or process argument ${quot(arg_name)} for method ${quot(method_name)} on interface ${quot(interface_name)}: ${message}`,
|
||||
},
|
||||
|
||||
// SLA
|
||||
'rate_limit_exceeded': {
|
||||
status: 429,
|
||||
|
54
src/backend/src/modules/web/APIErrorService.js
Normal file
54
src/backend/src/modules/web/APIErrorService.js
Normal file
@ -0,0 +1,54 @@
|
||||
const APIError = require("../../api/APIError");
|
||||
const BaseService = require("../../services/BaseService");
|
||||
|
||||
/**
|
||||
* @typedef {Object} ErrorSpec
|
||||
* @property {string} code - The error code
|
||||
* @property {string} status - HTTP status code
|
||||
* @property {function} message - A function that generates an error message
|
||||
*/
|
||||
|
||||
/**
|
||||
* The APIErrorService class provides a mechanism for registering and managing
|
||||
* error codes and messages which may be sent to clients.
|
||||
*
|
||||
* This allows for a single source-of-truth for error codes and messages that
|
||||
* are used by multiple services.
|
||||
*/
|
||||
class APIErrorService extends BaseService {
|
||||
_construct () {
|
||||
this.codes = {
|
||||
...this.constructor.codes,
|
||||
};
|
||||
}
|
||||
|
||||
// Hardcoded error codes from before this service was created
|
||||
static codes = APIError.codes;
|
||||
|
||||
/**
|
||||
* Registers API error codes.
|
||||
*
|
||||
* @param {Object.<string, ErrorSpec>} codes - A map of error codes to error specifications
|
||||
*/
|
||||
register (codes) {
|
||||
for ( const code in codes ) {
|
||||
this.codes[code] = codes[code];
|
||||
}
|
||||
}
|
||||
|
||||
create (code, fields) {
|
||||
const error_spec = this.codes[code];
|
||||
if ( ! error_spec ) {
|
||||
return new APIError(500, 'Missing error message.', null, {
|
||||
code,
|
||||
});
|
||||
}
|
||||
|
||||
return new APIError(error_spec.status, error_spec.message, null, {
|
||||
...fields,
|
||||
code,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = APIErrorService;
|
@ -19,6 +19,9 @@ class WebModule extends AdvancedBase {
|
||||
|
||||
const WebServerService = require("./WebServerService");
|
||||
services.registerService('web-server', WebServerService);
|
||||
|
||||
const APIErrorService = require("./APIErrorService");
|
||||
services.registerService('api-error', APIErrorService);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,8 @@ const { PermissionUtil } = require("../auth/PermissionService");
|
||||
const { Invoker } = require("../../../../putility/src/libs/invoker");
|
||||
const { get_user } = require("../../helpers");
|
||||
|
||||
const strutil = require('@heyputer/putility').libs.string;
|
||||
|
||||
/**
|
||||
* DriverService provides the functionality of Puter drivers.
|
||||
* This class is responsible for managing and interacting with Puter drivers.
|
||||
@ -50,8 +52,52 @@ class DriverService extends BaseService {
|
||||
this.interface_to_test_service = {};
|
||||
this.service_aliases = {};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is responsible for calling a driver's method with provided arguments.
|
||||
* It checks for permissions, selects the best option, and applies rate and monthly usage limits before invoking the driver.
|
||||
*
|
||||
* @param {Object} o - An object containing driver, interface, method, and arguments.
|
||||
* @returns {Promise<{success: boolean, service: DriverService.Driver, result: any, metadata: any}>}
|
||||
*/
|
||||
_init () {
|
||||
const svc_registry = this.services.get('registry');
|
||||
svc_registry.register_collection('');
|
||||
|
||||
const { quot } = strutil;
|
||||
const svc_apiError = this.services.get('api-error');
|
||||
svc_apiError.register({
|
||||
'missing_required_argument': {
|
||||
status: 400,
|
||||
message: ({ interface_name, method_name, arg_name }) =>
|
||||
`Missing required argument ${quot(arg_name)} for method ${quot(method_name)} on interface ${quot(interface_name)}`,
|
||||
},
|
||||
'argument_consolidation_failed': {
|
||||
status: 400,
|
||||
message: ({ interface_name, method_name, arg_name, message }) =>
|
||||
`Failed to parse or process argument ${quot(arg_name)} for method ${quot(method_name)} on interface ${quot(interface_name)}: ${message}`,
|
||||
},
|
||||
'interface_not_found': {
|
||||
status: 404,
|
||||
message: ({ interface_name }) => `Interface not found: ${quot(interface_name)}`,
|
||||
},
|
||||
'method_not_found': {
|
||||
status: 404,
|
||||
message: ({ interface_name, method_name }) => `Method not found: ${quot(method_name)} on interface ${quot(interface_name)}`,
|
||||
},
|
||||
'no_implementation_available': {
|
||||
status: 502,
|
||||
message: ({
|
||||
iface,
|
||||
interface_name,
|
||||
driver
|
||||
}) => `No implementation available for ` +
|
||||
(iface ?? interface_name) ? 'interface' : 'driver' +
|
||||
' ' + quot(iface ?? interface_name ?? driver) + '.',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for registering collections in the service registry.
|
||||
* It registers 'interfaces', 'drivers', and 'types' collections.
|
||||
@ -91,19 +137,6 @@ class DriverService extends BaseService {
|
||||
{ col_drivers });
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method is responsible for calling a driver's method with provided arguments.
|
||||
* It checks for permissions, selects the best option, and applies rate and monthly usage limits before invoking the driver.
|
||||
*
|
||||
* @param {Object} o - An object containing driver, interface, method, and arguments.
|
||||
* @returns {Promise<{success: boolean, service: DriverService.Driver, result: any, metadata: any}>}
|
||||
*/
|
||||
_init () {
|
||||
const svc_registry = this.services.get('registry');
|
||||
svc_registry.register_collection('');
|
||||
}
|
||||
|
||||
register_driver (interface_name, implementation) {
|
||||
this.interface_to_implementation[interface_name] = implementation;
|
||||
}
|
||||
@ -235,7 +268,8 @@ class DriverService extends BaseService {
|
||||
})();
|
||||
|
||||
if ( ! driver_service_exists ) {
|
||||
throw APIError.create('no_implementation_available', null, { iface })
|
||||
const svc_apiError = this.services.get('api-error');
|
||||
throw svc_apiError.create('no_implementation_available', { iface });
|
||||
}
|
||||
|
||||
const service = this.services.get(driver);
|
||||
@ -530,17 +564,19 @@ class DriverService extends BaseService {
|
||||
const svc_registry = this.services.get('registry');
|
||||
const c_interfaces = svc_registry.get('interfaces');
|
||||
const c_types = svc_registry.get('types');
|
||||
|
||||
const svc_apiError = this.services.get('api-error');
|
||||
|
||||
// Note: 'interface' is a strict mode reserved word.
|
||||
const interface_ = c_interfaces.get(interface_name);
|
||||
if ( ! interface_ ) {
|
||||
throw APIError.create('interface_not_found', null, { interface_name });
|
||||
throw svc_apiError.create('interface_not_found', { interface_name });
|
||||
}
|
||||
|
||||
const processed_args = {};
|
||||
const method = interface_.methods[method_name];
|
||||
if ( ! method ) {
|
||||
throw APIError.create('method_not_found', null, { interface_name, method_name });
|
||||
throw svc_apiError.create('method_not_found', { interface_name, method_name });
|
||||
}
|
||||
|
||||
for ( const [arg_name, arg_descriptor] of Object.entries(method.parameters) ) {
|
||||
@ -551,7 +587,7 @@ class DriverService extends BaseService {
|
||||
// There's a particular way I want to do this that involves
|
||||
// a trait for extensible behaviour.
|
||||
if ( arg_value === undefined && arg_descriptor.required ) {
|
||||
throw APIError.create('missing_required_argument', null, {
|
||||
throw svc_apiError.create('missing_required_argument', {
|
||||
interface_name,
|
||||
method_name,
|
||||
arg_name,
|
||||
@ -564,7 +600,7 @@ class DriverService extends BaseService {
|
||||
processed_args[arg_name] = await arg_behaviour.consolidate(
|
||||
ctx, arg_value, { arg_descriptor, arg_name });
|
||||
} catch ( e ) {
|
||||
throw APIError.create('argument_consolidation_failed', null, {
|
||||
throw svc_apiError.create('argument_consolidation_failed', {
|
||||
interface_name,
|
||||
method_name,
|
||||
arg_name,
|
||||
|
Loading…
Reference in New Issue
Block a user