lib/core/decorators/resource.js
'use strict';
const upath = require('upath');
const $err = require('../../util/application_error');
const Metadata = require('../../util/meta');
/**
* What might be referred to as a *controller* in other frameworks, a `Resource` module defines HTTP
* methods on an endpoint, supporting the session-per-request transaction pattern via Ravel
* middleware. `Resource`s also support dependency injection, allowing for the easy creation of RESTful
* interfaces to your `Module`-based application logic. Resources are really just a thin
* wrapper around `Routes`, using specially-named handler functions (`get`, `getAll`, `head`, `headAll`,
* `post`, `put`, `putAll`, `patch`, `patchAll`, `delete`, `deleteAll`) instead of `@mapping`. This
* convention-over-configuration approach makes it easier to write proper REST APIs with less code,
* and is recommended over "carefully chosen" `@mapping`s in a `Routes` class.
*
* Omitting any or all of the specially-named handler functions is fine, and will result in a
* `501 NOT IMPLEMENTED` status when that particular method/endpoint is requested.
*
* `Resource`s inherit all the properties, methods and decorators of `Routes`. See [`Routes`](#routes)
* for more information. Note that `@mapping` does not apply to `Resources`.
*
* For details concerning path construction, including how to employ parameters and regular expressions,
* see [`Routes`](#routes).
*
* @param {string} basePath - The base path for all endpoints in this class. Should be unique within an application.
* @example
* const inject = require('ravel').inject;
* const Resource = require('ravel').Resource;
* const before = Resource.before;
*
* // you can inject your own Modules and npm dependencies into Resources
* // @Resource('/person')
* // @inject(koa-bodyparser', 'fs', 'custom-module')
* class PersonResource {
* constructor (bodyParser, fs, custom) {
* this.bodyParser = bodyParser(); // make bodyParser middleware available
* this.fs = fs;
* this.custom = custom;
* }
*
* // will map to GET /person
* // @before('bodyParser') // use bodyParser middleware before handler
* async getAll (ctx) {
* // ctx is a koa context object.
* // await on Promises, and set ctx.body to create a body for response
* // "OK" status code will be chosen automatically unless configured via ctx.status
* // Extend and throw a Ravel.Error to send an error status code
* }
*
* // will map to GET /person/:id
* async get (ctx) {
* // can use ctx.params.id in here automatically
* }
*
* // will map to HEAD /person
* async headAll (ctx) {}
*
* // will map to HEAD /person/:id
* async head (ctx) {}
*
* // will map to POST /person
* async post (ctx) {}
*
* // will map to PUT /person
* async putAll (ctx) {}
*
* // will map to PUT /person/:id
* async put (ctx) {}
*
* // will map to PATCH /person
* async patchAll (ctx) {}
*
* // will map to PATCH /person/:id
* async patch (ctx) {}
*
* // will map to DELETE /person
* async deleteAll (ctx) {}
*
* // will map to DELETE /person/:id
* async delete (ctx) {}
* }
*
* module.exports = PersonResource;
*/
const Resource = function (basePath) {
if (typeof basePath !== 'string') {
throw new $err.IllegalValue(
'@Resource must be used with a basePath, as in @Resource(\'/route/base/path\')');
}
// normalize and validate base path
const bp = upath.toUnix(upath.posix.normalize(basePath));
return function (target, key, descriptor) {
Metadata.putClassMeta(target.prototype, '@role', 'type', 'Resource');
Metadata.putClassMeta(target.prototype, '@role', 'name', bp);
};
};
/*!
* Populates a class with a static reference to the // @Resource role decorator
*/
module.exports = Resource;