diff --git a/.travis.yml b/.travis.yml
index bb543f0..ce21122 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,11 @@
+sudo: false
language: node_js
node_js:
- - "6"
- - "7"
- - "8"
-notifications:
- email:
- on_success: never
+ - '8'
+ - '10'
+install:
+ - npm i npminstall && npminstall
+script:
+ - npm run ci
+after_script:
+ - npminstall codecov && codecov
diff --git a/LICENSE b/LICENSE
index 01750af..a488e9c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2015 Alexander C. Mingoia
+Copyright (c) 2019 eggjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 9222701..f9b5485 100644
--- a/README.md
+++ b/README.md
@@ -1,59 +1,34 @@
-# koa-router
+# @eggjs/router
-[![NPM version](https://img.shields.io/npm/v/koa-router.svg?style=flat)](https://npmjs.org/package/koa-router) [![NPM Downloads](https://img.shields.io/npm/dm/koa-router.svg?style=flat)](https://npmjs.org/package/koa-router) [![Node.js Version](https://img.shields.io/node/v/koa-router.svg?style=flat)](http://nodejs.org/download/) [![Build Status](https://img.shields.io/travis/alexmingoia/koa-router.svg?style=flat)](http://travis-ci.org/alexmingoia/koa-router) [![Tips](https://img.shields.io/gratipay/alexmingoia.svg?style=flat)](https://www.gratipay.com/alexmingoia/) [![Gitter Chat](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat)](https://gitter.im/alexmingoia/koa-router/)
+Router core component for [Egg.js](https://github.com/eggjs).
-> Router middleware for [koa](https://github.com/koajs/koa)
+> **This repository is a fork of [koa-router](https://github.com/alexmingoia/koa-router).** with some additional features.
-* Express-style routing using `app.get`, `app.put`, `app.post`, etc.
-* Named URL parameters.
-* Named routes with URL generation.
-* Responds to `OPTIONS` requests with allowed methods.
-* Support for `405 Method Not Allowed` and `501 Not Implemented`.
-* Multiple route middleware.
-* Multiple routers.
-* Nestable routers.
-* ES7 async/await support.
-
-## Migrating to 7 / Koa 2
-
-- The API has changed to match the new promise-based middleware
- signature of koa 2. See the
- [koa 2.x readme](https://github.com/koajs/koa/tree/2.0.0-alpha.3) for more
- information.
-- Middleware is now always run in the order declared by `.use()` (or `.get()`,
- etc.), which matches Express 4 API.
-
-## Installation
-
-Install using [npm](https://www.npmjs.org/):
-
-```sh
-npm install koa-router
-```
+> And thanks for the greate work of @alexmingoia and the original team.
## API Reference
-
-* [koa-router](#module_koa-router)
- * [Router](#exp_module_koa-router--Router) ⏏
- * [new Router([opts])](#new_module_koa-router--Router_new)
+
+* [egg-router](#module_egg-router)
+ * [Router](#exp_module_egg-router--Router) ⏏
+ * [new Router([opts])](#new_module_egg-router--Router_new)
* _instance_
- * [.get|put|post|patch|delete|del](#module_koa-router--Router+get|put|post|patch|delete|del) ⇒ Router
- * [.routes](#module_koa-router--Router+routes) ⇒ function
- * [.use([path], middleware)](#module_koa-router--Router+use) ⇒ Router
- * [.prefix(prefix)](#module_koa-router--Router+prefix) ⇒ Router
- * [.allowedMethods([options])](#module_koa-router--Router+allowedMethods) ⇒ function
- * [.redirect(source, destination, [code])](#module_koa-router--Router+redirect) ⇒ Router
- * [.route(name)](#module_koa-router--Router+route) ⇒ Layer
| false
- * [.url(name, params, [options])](#module_koa-router--Router+url) ⇒ String
| Error
- * [.param(param, middleware)](#module_koa-router--Router+param) ⇒ Router
+ * [.get|put|post|patch|delete|del](#module_egg-router--Router+get|put|post|patch|delete|del) ⇒ Router
+ * [.routes](#module_egg-router--Router+routes) ⇒ function
+ * [.use([path], middleware)](#module_egg-router--Router+use) ⇒ Router
+ * [.prefix(prefix)](#module_egg-router--Router+prefix) ⇒ Router
+ * [.allowedMethods([options])](#module_egg-router--Router+allowedMethods) ⇒ function
+ * [.redirect(source, destination, [code])](#module_egg-router--Router+redirect) ⇒ Router
+ * [.route(name)](#module_egg-router--Router+route) ⇒ Layer
| false
+ * [.url(name, params, [options])](#module_egg-router--Router+url) ⇒ String
| Error
+ * [.param(param, middleware)](#module_egg-router--Router+param) ⇒ Router
* _static_
- * [.url(path, params)](#module_koa-router--Router.url) ⇒ String
+ * [.url(path, params)](#module_egg-router--Router.url) ⇒ String
-
+
### Router ⏏
-**Kind**: Exported class
-
+**Kind**: Exported class
+
#### new Router([opts])
Create a new router.
@@ -64,12 +39,12 @@ Create a new router.
| [opts] | Object
| |
| [opts.prefix] | String
| prefix router paths |
-**Example**
+**Example**
Basic usage:
```javascript
var Koa = require('koa');
-var Router = require('koa-router');
+var Router = require('@eggjs/router');
var app = new Koa();
var router = new Router();
@@ -82,7 +57,7 @@ app
.use(router.routes())
.use(router.allowedMethods());
```
-
+
#### router.get|put|post|patch|delete|del ⇒ Router
Create `router.verb()` methods, where *verb* is one of the HTTP verbs such
@@ -197,7 +172,7 @@ router.get('/:category/:title', (ctx, next) => {
The [path-to-regexp](https://github.com/pillarjs/path-to-regexp) module is
used to convert paths to regular expressions.
-**Kind**: instance property of [Router](#exp_module_koa-router--Router)
+**Kind**: instance property of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
| --- | --- | --- |
@@ -205,13 +180,13 @@ used to convert paths to regular expressions.
| [middleware] | function
| route middleware(s) |
| callback | function
| route callback |
-
+
#### router.routes ⇒ function
Returns router middleware which dispatches a route matching the request.
-**Kind**: instance property of [Router](#exp_module_koa-router--Router)
-
+**Kind**: instance property of [Router](#exp_module_egg-router--Router)
+
#### router.use([path], middleware) ⇒ Router
Use given middleware.
@@ -220,15 +195,15 @@ Middleware run in the order they are defined by `.use()`. They are invoked
sequentially, requests start at the first middleware and work their way
"down" the middleware stack.
-**Kind**: instance method of [Router](#exp_module_koa-router--Router)
+**Kind**: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type |
| --- | --- |
-| [path] | String
|
-| middleware | function
|
-| [...] | function
|
+| [path] | String
|
+| middleware | function
|
+| [...] | function
|
-**Example**
+**Example**
```javascript
// session middleware will run before authorize
router
@@ -243,29 +218,29 @@ router.use(['/users', '/admin'], userAuth());
app.use(router.routes());
```
-
+
#### router.prefix(prefix) ⇒ Router
Set the path prefix for a Router instance that was already initialized.
-**Kind**: instance method of [Router](#exp_module_koa-router--Router)
+**Kind**: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type |
| --- | --- |
-| prefix | String
|
+| prefix | String
|
-**Example**
+**Example**
```javascript
router.prefix('/things/:thing_id')
```
-
+
#### router.allowedMethods([options]) ⇒ function
Returns separate middleware for responding to `OPTIONS` requests with
an `Allow` header containing the allowed methods, as well as responding
with `405 Method Not Allowed` and `501 Not Implemented` as appropriate.
-**Kind**: instance method of [Router](#exp_module_koa-router--Router)
+**Kind**: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
| --- | --- | --- |
@@ -274,10 +249,10 @@ with `405 Method Not Allowed` and `501 Not Implemented` as appropriate.
| [options.notImplemented] | function
| throw the returned value in place of the default NotImplemented error |
| [options.methodNotAllowed] | function
| throw the returned value in place of the default MethodNotAllowed error |
-**Example**
+**Example**
```javascript
var Koa = require('koa');
-var Router = require('koa-router');
+var Router = require('egg-router');
var app = new Koa();
var router = new Router();
@@ -290,7 +265,7 @@ app.use(router.allowedMethods());
```javascript
var Koa = require('koa');
-var Router = require('koa-router');
+var Router = require('egg-router');
var Boom = require('boom');
var app = new Koa();
@@ -303,7 +278,7 @@ app.use(router.allowedMethods({
methodNotAllowed: () => new Boom.methodNotAllowed()
}));
```
-
+
#### router.redirect(source, destination, [code]) ⇒ Router
Redirect `source` to `destination` URL with optional 30x status `code`.
@@ -323,7 +298,7 @@ router.all('/login', ctx => {
});
```
-**Kind**: instance method of [Router](#exp_module_koa-router--Router)
+**Kind**: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
| --- | --- | --- |
@@ -331,23 +306,23 @@ router.all('/login', ctx => {
| destination | String
| URL or route name. |
| [code] | Number
| HTTP status code (default: 301). |
-
+
#### router.route(name) ⇒ Layer
| false
Lookup route with given `name`.
-**Kind**: instance method of [Router](#exp_module_koa-router--Router)
+**Kind**: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type |
| --- | --- |
-| name | String
|
+| name | String
|
-
+
#### router.url(name, params, [options]) ⇒ String
| Error
Generate URL for route. Takes a route name and map of named `params`.
-**Kind**: instance method of [Router](#exp_module_koa-router--Router)
+**Kind**: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
| --- | --- | --- |
@@ -356,7 +331,7 @@ Generate URL for route. Takes a route name and map of named `params`.
| [options] | Object
| options parameter |
| [options.query] | Object
| String
| query options |
-**Example**
+**Example**
```javascript
router.get('user', '/users/:id', (ctx, next) => {
// ...
@@ -379,20 +354,20 @@ router.url('user', { id: 3 }, { query: { limit: 1 } });
router.url('user', { id: 3 }, { query: "limit=1" });
// => "/users/3?limit=1"
```
-
+
#### router.param(param, middleware) ⇒ Router
Run middleware for named route parameters. Useful for auto-loading or
validation.
-**Kind**: instance method of [Router](#exp_module_koa-router--Router)
+**Kind**: instance method of [Router](#exp_module_egg-router--Router)
| Param | Type |
| --- | --- |
-| param | String
|
-| middleware | function
|
+| param | String
|
+| middleware | function
|
-**Example**
+**Example**
```javascript
router
.param('user', (id, ctx, next) => {
@@ -411,12 +386,12 @@ router
// /users/3 => {"id": 3, "name": "Alex"}
// /users/3/friends => [{"id": 4, "name": "TJ"}]
```
-
+
#### Router.url(path, params [, options]) ⇒ String
Generate URL from url pattern and given `params`.
-**Kind**: static method of [Router](#exp_module_koa-router--Router)
+**Kind**: static method of [Router](#exp_module_egg-router--Router)
| Param | Type | Description |
| --- | --- | --- |
@@ -425,7 +400,7 @@ Generate URL from url pattern and given `params`.
| [options] | Object
| options parameter |
| [options.query] | Object
| String
| query options |
-**Example**
+**Example**
```javascript
var url = Router.url('/users/:id', {id: 1});
// => "/users/1"
@@ -433,14 +408,7 @@ var url = Router.url('/users/:id', {id: 1});
const url = Router.url('/users/:id', {id: 1}, {query: { active: true }});
// => "/users/1?active=true"
```
-## Contributing
-
-Please submit all issues and pull requests to the [alexmingoia/koa-router](http://github.com/alexmingoia/koa-router) repository!
## Tests
Run tests using `npm test`.
-
-## Support
-
-If you have any problem or suggestion please open an issue [here](https://github.com/alexmingoia/koa-router/issues).
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..981e82b
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,15 @@
+environment:
+ matrix:
+ - nodejs_version: '8'
+ - nodejs_version: '10'
+
+install:
+ - ps: Install-Product node $env:nodejs_version
+ - npm i npminstall && node_modules\.bin\npminstall
+
+test_script:
+ - node --version
+ - npm --version
+ - npm run test
+
+build: off
diff --git a/history.md b/history.md
index af49f35..a1d7b75 100644
--- a/history.md
+++ b/history.md
@@ -1,4 +1,28 @@
-# History
+
+1.1.0 / 2019-01-30
+==================
+
+**features**
+ * [[`b318dd5`](http://github.com/eggjs/egg-router/commit/b318dd5ff2eea60013ed62b2a435f803c12bf20f)] - feat: add egg-router (dead-horse <>)
+
+**fixes**
+ * [[`b3db7b4`](http://github.com/eggjs/egg-router/commit/b3db7b41988d3bf1b4e885ed76b3a8165c1d3b1d)] - fix: add missing dependencies koa-convert (dead-horse <>)
+ * [[`c280336`](http://github.com/eggjs/egg-router/commit/c2803368a8256bc9504ae3c821eefeb9d1fcbc4d)] - fix: only support node@8 (dead-horse <>)
+ * [[`7a887a2`](http://github.com/eggjs/egg-router/commit/7a887a252445e602c2ea7e1c6f4cde52a433644d)] - fix: update license (dead-horse <>)
+
+**others**
+ * [[`ae40168`](http://github.com/eggjs/egg-router/commit/ae4016862fb8bdaf30e730a9278fbb1455d8b75d)] - docs: clean doc (dead-horse <>)
+ * [[`e4f21a8`](http://github.com/eggjs/egg-router/commit/e4f21a8ed6dfd72c4d6f4a13bbda9a4e90b0401c)] - chore: fix history (dead-horse <>)
+
+1.0.0 / 2019-01-30
+==================
+
+**others**
+ * [[`d6496e0`](http://github.com/eggjs/egg-router/commit/d6496e09be6b0f91dcb96611f31ec5ab6ad8ac78)] - refactor: rename to @eggjs/router (dead-horse <>)
+
+-------------------------------
+
+# Release History from koa-router
## 7.4.0
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..7a9f149
--- /dev/null
+++ b/index.js
@@ -0,0 +1,10 @@
+'use strict';
+
+const KoaRouter = require('./lib/router');
+const EggRouter = require('./lib/egg_router');
+
+// for compact
+module.exports = KoaRouter;
+module.exports.KoaRouter = KoaRouter;
+module.exports.EggRouter = EggRouter;
+
diff --git a/lib/README_tpl.hbs b/lib/README_tpl.hbs
deleted file mode 100644
index e23d8c9..0000000
--- a/lib/README_tpl.hbs
+++ /dev/null
@@ -1,51 +0,0 @@
-# koa-router
-
-[![NPM version](https://img.shields.io/npm/v/koa-router.svg?style=flat)](https://npmjs.org/package/koa-router) [![NPM Downloads](https://img.shields.io/npm/dm/koa-router.svg?style=flat)](https://npmjs.org/package/koa-router) [![Node.js Version](https://img.shields.io/node/v/koa-router.svg?style=flat)](http://nodejs.org/download/) [![Build Status](https://img.shields.io/travis/alexmingoia/koa-router.svg?style=flat)](http://travis-ci.org/alexmingoia/koa-router) [![Tips](https://img.shields.io/gratipay/alexmingoia.svg?style=flat)](https://www.gratipay.com/alexmingoia/) [![Gitter Chat](https://img.shields.io/badge/gitter-join%20chat-1dce73.svg?style=flat)](https://gitter.im/alexmingoia/koa-router/)
-
-> Router middleware for [koa](https://github.com/koajs/koa)
-
-* Express-style routing using `app.get`, `app.put`, `app.post`, etc.
-* Named URL parameters.
-* Named routes with URL generation.
-* Responds to `OPTIONS` requests with allowed methods.
-* Support for `405 Method Not Allowed` and `501 Not Implemented`.
-* Multiple route middleware.
-* Multiple routers.
-* Nestable routers.
-* ES7 async/await support.
-
-{{#module name="koa-router"}}{{>body}}{{/module}}## Migrating to 7 / Koa 2
-
-- The API has changed to match the new promise-based middleware
- signature of koa 2. See the
- [koa 2.x readme](https://github.com/koajs/koa/tree/2.0.0-alpha.3) for more
- information.
-- Middleware is now always run in the order declared by `.use()` (or `.get()`,
- etc.), which matches Express 4 API.
-
-## Installation
-
-Install using [npm](https://www.npmjs.org/):
-
-```sh
-npm install koa-router
-```
-
-## API Reference
-{{#module name="koa-router"~}}
- {{>body~}}
- {{>member-index~}}
- {{>members~}}
-{{/module~}}
-
-## Contributing
-
-Please submit all issues and pull requests to the [alexmingoia/koa-router](http://github.com/alexmingoia/koa-router) repository!
-
-## Tests
-
-Run tests using `npm test`.
-
-## Support
-
-If you have any problem or suggestion please open an issue [here](https://github.com/alexmingoia/koa-router/issues).
diff --git a/lib/egg_router.js b/lib/egg_router.js
new file mode 100644
index 0000000..e94f07b
--- /dev/null
+++ b/lib/egg_router.js
@@ -0,0 +1,327 @@
+'use strict';
+
+const is = require('is-type-of');
+const Router = require('./router');
+const utility = require('utility');
+const inflection = require('inflection');
+const assert = require('assert');
+const utils = require('./utils');
+
+const METHODS = [ 'head', 'options', 'get', 'put', 'patch', 'post', 'delete' ];
+
+const REST_MAP = {
+ index: {
+ suffix: '',
+ method: 'GET',
+ },
+ new: {
+ namePrefix: 'new_',
+ member: true,
+ suffix: 'new',
+ method: 'GET',
+ },
+ create: {
+ suffix: '',
+ method: 'POST',
+ },
+ show: {
+ member: true,
+ suffix: ':id',
+ method: 'GET',
+ },
+ edit: {
+ member: true,
+ namePrefix: 'edit_',
+ suffix: ':id/edit',
+ method: 'GET',
+ },
+ update: {
+ member: true,
+ namePrefix: '',
+ suffix: ':id',
+ method: ['PATCH', 'PUT'],
+ },
+ destroy: {
+ member: true,
+ namePrefix: 'destroy_',
+ suffix: ':id',
+ method: 'DELETE',
+ },
+};
+
+/**
+ * FIXME: move these patch into @eggjs/router
+ */
+class EggRouter extends Router {
+
+ /**
+ * @constructor
+ * @param {Object} opts - Router options.
+ * @param {Application} app - Application object.
+ */
+ constructor(opts, app) {
+ super(opts);
+ this.app = app;
+ this.patchRouterMethod();
+ }
+
+ patchRouterMethod() {
+ // patch router methods to support generator function middleware and string controller
+ METHODS.concat(['all']).forEach(method => {
+ this[method] = (...args) => {
+ const splited = spliteAndResolveRouterParams({ args, app: this.app });
+ // format and rebuild params
+ args = splited.prefix.concat(splited.middlewares);
+ return super[method](...args);
+ };
+ });
+ }
+
+ /**
+ * Create and register a route.
+ * @param {String} path - url path
+ * @param {Array} methods - Array of HTTP verbs
+ * @param {Array} middlewares -
+ * @param {Object} opts -
+ * @return {Route} this
+ */
+ register(path, methods, middlewares, opts) {
+ // patch register to support generator function middleware and string controller
+ middlewares = Array.isArray(middlewares) ? middlewares : [middlewares];
+ middlewares = convertMiddlewares(middlewares, this.app);
+ path = Array.isArray(path) ? path : [path];
+ path.forEach(p => super.register(p, methods, middlewares, opts));
+ return this;
+ }
+
+ /**
+ * restful router api
+ * @param {String} name - Router name
+ * @param {String} prefix - url prefix
+ * @param {Function} middleware - middleware or controller
+ * @example
+ * ```js
+ * app.resources('/posts', 'posts')
+ * app.resources('posts', '/posts', 'posts')
+ * app.resources('posts', '/posts', app.role.can('user'), app.controller.posts)
+ * ```
+ *
+ * Examples:
+ *
+ * ```js
+ * app.resources('/posts', 'posts')
+ * ```
+ *
+ * yield router mapping
+ *
+ * Method | Path | Route Name | Controller.Action
+ * -------|-----------------|----------------|-----------------------------
+ * GET | /posts | posts | app.controller.posts.index
+ * GET | /posts/new | new_post | app.controller.posts.new
+ * GET | /posts/:id | post | app.controller.posts.show
+ * GET | /posts/:id/edit | edit_post | app.controller.posts.edit
+ * POST | /posts | posts | app.controller.posts.create
+ * PATCH | /posts/:id | post | app.controller.posts.update
+ * DELETE | /posts/:id | post | app.controller.posts.destroy
+ *
+ * app.router.url can generate url based on arguments
+ * ```js
+ * app.router.url('posts')
+ * => /posts
+ * app.router.url('post', { id: 1 })
+ * => /posts/1
+ * app.router.url('new_post')
+ * => /posts/new
+ * app.router.url('edit_post', { id: 1 })
+ * => /posts/1/edit
+ * ```
+ * @return {Router} return route object.
+ * @since 1.0.0
+ */
+ resources(...args) {
+ const splited = spliteAndResolveRouterParams({ args, app: this.app });
+ const middlewares = splited.middlewares;
+ // last argument is Controller object
+ const controller = splited.middlewares.pop();
+
+ let name = '';
+ let prefix = '';
+ if (splited.prefix.length === 2) {
+ // router.get('users', '/users')
+ name = splited.prefix[0];
+ prefix = splited.prefix[1];
+ } else {
+ // router.get('/users')
+ prefix = splited.prefix[0];
+ }
+
+ for (const key in REST_MAP) {
+ const action = controller[key];
+ if (!action) continue;
+
+ const opts = REST_MAP[key];
+ let formatedName;
+ if (opts.member) {
+ formatedName = inflection.singularize(name);
+ } else {
+ formatedName = inflection.pluralize(name);
+ }
+ if (opts.namePrefix) {
+ formatedName = opts.namePrefix + formatedName;
+ }
+ prefix = prefix.replace(/\/$/, '');
+ const path = opts.suffix ? `${prefix}/${opts.suffix}` : prefix;
+ const method = Array.isArray(opts.method) ? opts.method : [opts.method];
+ this.register(path, method, middlewares.concat(action), { name: formatedName });
+ }
+
+ return this;
+ }
+
+ /**
+ * @param {String} name - Router name
+ * @param {Object} params - more parameters
+ * @example
+ * ```js
+ * router.url('edit_post', { id: 1, name: 'foo', page: 2 })
+ * => /posts/1/edit?name=foo&page=2
+ * router.url('posts', { name: 'foo&1', page: 2 })
+ * => /posts?name=foo%261&page=2
+ * ```
+ * @return {String} url by path name and query params.
+ * @since 1.0.0
+ */
+ url(name, params) {
+ const route = this.route(name);
+ if (!route) return '';
+
+ const args = params;
+ let url = route.path;
+
+ assert(!is.regExp(url), `Can't get the url for regExp ${url} for by name '${name}'`);
+
+ const queries = [];
+ if (typeof args === 'object' && args !== null) {
+ const replacedParams = [];
+ url = url.replace(/:([a-zA-Z_]\w*)/g, function ($0, key) {
+ if (utility.has(args, key)) {
+ const values = args[key];
+ replacedParams.push(key);
+ return utility.encodeURIComponent(Array.isArray(values) ? values[0] : values);
+ }
+ return $0;
+ });
+
+ for (const key in args) {
+ if (replacedParams.includes(key)) {
+ continue;
+ }
+
+ const values = args[key];
+ const encodedKey = utility.encodeURIComponent(key);
+ if (Array.isArray(values)) {
+ for (const val of values) {
+ queries.push(`${encodedKey}=${utility.encodeURIComponent(val)}`);
+ }
+ } else {
+ queries.push(`${encodedKey}=${utility.encodeURIComponent(values)}`);
+ }
+ }
+ }
+
+ if (queries.length > 0) {
+ const queryStr = queries.join('&');
+ if (!url.includes('?')) {
+ url = `${url}?${queryStr}`;
+ } else {
+ url = `${url}&${queryStr}`;
+ }
+ }
+
+ return url;
+ }
+
+ pathFor(name, params) {
+ return this.url(name, params);
+ }
+}
+
+/**
+ * 1. split (name, url, ...middleware, controller) to
+ * {
+ * prefix: [name, url]
+ * middlewares [...middleware, controller]
+ * }
+ *
+ * 2. resolve controller from string to function
+ *
+ * @param {Object} options inputs
+ * @param {Object} options.args router params
+ * @param {Object} options.app egg application instance
+ * @return {Object} prefix and middlewares
+ */
+function spliteAndResolveRouterParams({ args, app }) {
+ let prefix;
+ let middlewares;
+ if (args.length >= 3 && (is.string(args[1]) || is.regExp(args[1]))) {
+ // app.get(name, url, [...middleware], controller)
+ prefix = args.slice(0, 2);
+ middlewares = args.slice(2);
+ } else {
+ // app.get(url, [...middleware], controller)
+ prefix = args.slice(0, 1);
+ middlewares = args.slice(1);
+ }
+ // resolve controller
+ const controller = middlewares.pop();
+ middlewares.push(resolveController(controller, app));
+ return { prefix, middlewares };
+}
+
+/**
+ * resolve controller from string to function
+ * @param {String|Function} controller input controller
+ * @param {Application} app egg application instance
+ * @return {Function} controller function
+ */
+function resolveController(controller, app) {
+ if (is.string(controller)) {
+ const actions = controller.split('.');
+ let obj = app.controller;
+ actions.forEach(key => {
+ obj = obj[key];
+ if (!obj) throw new Error(`controller '${controller}' not exists`);
+ });
+ controller = obj;
+ }
+ // ensure controller is exists
+ if (!controller) throw new Error('controller not exists');
+ return controller;
+}
+
+/**
+ * 1. ensure controller(last argument) support string
+ * - [url, controller]: app.get('/home', 'home');
+ * - [name, url, controller(string)]: app.get('posts', '/posts', 'posts.list');
+ * - [name, url, controller]: app.get('posts', '/posts', app.controller.posts.list);
+ * - [name, url(regexp), controller]: app.get('regRouter', /\/home\/index/, 'home.index');
+ * - [name, url, middleware, [...], controller]: `app.get(/user/:id', hasLogin, canGetUser, 'user.show');`
+ *
+ * 2. make middleware support generator function
+ *
+ * @param {Array} middlewares middlewares and controller(last middleware)
+ * @param {Application} app egg application instance
+ * @return {Array} middlewares
+ */
+function convertMiddlewares(middlewares, app) {
+ // ensure controller is resolved
+ const controller = resolveController(middlewares.pop(), app);
+ // make middleware support generator function
+ middlewares = middlewares.map(utils.middleware);
+ const wrappedController = (ctx, next) => {
+ return utils.callFn(controller, [ctx, next], ctx);
+ };
+ return middlewares.concat([wrappedController]);
+}
+
+module.exports = EggRouter;
diff --git a/lib/layer.js b/lib/layer.js
index e24808a..f96d0e4 100644
--- a/lib/layer.js
+++ b/lib/layer.js
@@ -1,4 +1,4 @@
-var debug = require('debug')('koa-router');
+var debug = require('debug')('egg-router');
var pathToRegExp = require('path-to-regexp');
var uri = require('urijs');
diff --git a/lib/router.js b/lib/router.js
index fe1ef44..48f81d8 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -1,11 +1,8 @@
/**
- * RESTful resource routing middleware for koa.
- *
- * @author Alex Mingoia
- * @link https://github.com/alexmingoia/koa-router
+ * RESTful resource routing middleware for eggjs.
*/
-var debug = require('debug')('koa-router');
+var debug = require('debug')('egg-router');
var compose = require('koa-compose');
var HttpError = require('http-errors');
var methods = require('methods');
@@ -343,6 +340,7 @@ Router.prototype.routes = Router.prototype.middleware = function () {
ctx.captures = layer.captures(path, ctx.captures);
ctx.params = layer.params(path, ctx.captures, ctx.params);
ctx.routerName = layer.name;
+ ctx.routerPath = layer.path;
return next();
});
return memo.concat(layer.stack);
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..f28c546
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,18 @@
+'use strict';
+
+const convert = require('koa-convert');
+const is = require('is-type-of');
+const co = require('co');
+
+module.exports = {
+ async callFn(fn, args, ctx) {
+ args = args || [];
+ if (!is.function(fn)) return;
+ if (is.generatorFunction(fn)) fn = co.wrap(fn);
+ return ctx ? fn.call(ctx, ...args) : fn(...args);
+ },
+
+ middleware(fn) {
+ return is.generatorFunction(fn) ? convert(fn) : fn;
+ },
+};
diff --git a/package.json b/package.json
index 4243eda..cd31115 100644
--- a/package.json
+++ b/package.json
@@ -1,16 +1,16 @@
{
- "name": "koa-router",
- "description": "Router middleware for koa. Provides RESTful resource routing.",
+ "name": "@eggjs/router",
+ "description": "Router middleware for egg/koa. Provides RESTful resource routing.",
"repository": {
"type": "git",
- "url": "https://github.com/alexmingoia/koa-router.git"
+ "url": "https://github.com/eggjs/egg-router.git"
},
- "main": "lib/router.js",
"files": [
- "lib"
+ "lib",
+ "index.js"
],
- "author": "Alex Mingoia ",
- "version": "7.4.0",
+ "author": "eggjs",
+ "version": "1.1.0",
"keywords": [
"koa",
"middleware",
@@ -18,27 +18,36 @@
"route"
],
"dependencies": {
+ "co": "^4.6.0",
"debug": "^3.1.0",
"http-errors": "^1.3.1",
+ "inflection": "^1.12.0",
+ "is-type-of": "^1.2.1",
"koa-compose": "^3.0.0",
+ "koa-convert": "^1.2.0",
"methods": "^1.0.1",
"path-to-regexp": "^1.1.1",
- "urijs": "^1.19.0"
+ "urijs": "^1.19.0",
+ "utility": "^1.15.0"
},
"devDependencies": {
+ "egg-bin": "^4.10.0",
+ "egg-ci": "^1.11.0",
"expect.js": "^0.3.1",
- "jsdoc-to-markdown": "^1.1.1",
"koa": "2.2.0",
"mocha": "^2.0.1",
"should": "^6.0.3",
"supertest": "^1.0.1"
},
"scripts": {
- "test": "NODE_ENV=test mocha test/**/*.js",
- "docs": "NODE_ENV=test jsdoc2md -t ./lib/README_tpl.hbs --src ./lib/*.js >| README.md"
+ "test": "egg-bin test",
+ "ci": "egg-bin cov"
+ },
+ "ci": {
+ "version": "8, 10"
},
"engines": {
- "node": ">= 4"
+ "node": ">= 8"
},
"license": "MIT"
}
diff --git a/test/index.js b/test/index.js
deleted file mode 100644
index 46388ae..0000000
--- a/test/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Module tests
- */
-
-var koa = require('koa')
- , should = require('should');
-
-describe('module', function() {
- it('should expose Router', function(done) {
- var Router = require('..');
- should.exist(Router);
- Router.should.be.type('function');
- done();
- });
-});
diff --git a/test/index.test.js b/test/index.test.js
new file mode 100644
index 0000000..bf0448a
--- /dev/null
+++ b/test/index.test.js
@@ -0,0 +1,14 @@
+/**
+ * Module tests
+ */
+
+const assert = require('assert');
+const Router = require('..');
+
+describe('test/index.test.js', () => {
+ it('should expose Router', () => {
+ assert(typeof Router === 'function');
+ assert(typeof Router.KoaRouter === 'function');
+ assert(typeof Router.EggRouter === 'function');
+ });
+});
diff --git a/test/lib/egg_router.test.js b/test/lib/egg_router.test.js
new file mode 100644
index 0000000..c0b3a35
--- /dev/null
+++ b/test/lib/egg_router.test.js
@@ -0,0 +1,189 @@
+'use strict';
+
+const EggRouter = require('../../').EggRouter;
+const assert = require('assert');
+const is = require('is-type-of');
+
+describe('test/lib/egg_router.test.js', () => {
+ it('creates new router with egg app', function () {
+ const app = { controller: {} };
+ const router = new EggRouter({}, app);
+ assert(router);
+ [ 'head', 'options', 'get', 'put', 'patch', 'post', 'delete', 'all', 'resources' ].forEach(method => {
+ assert(typeof router[method] === 'function');
+ });
+ });
+
+ it('should app.verb(url, controller) work', () => {
+ const app = {
+ controller: {
+ async foo() {},
+ hello: {
+ * world() {},
+ },
+ },
+ };
+
+ const router = new EggRouter({}, app);
+ router.get('/foo', app.controller.foo);
+ router.post('/hello/world', app.controller.hello.world);
+
+ assert(router.stack[0].path === '/foo');
+ assert.deepEqual(router.stack[0].methods, [ 'HEAD', 'GET' ]);
+ assert(router.stack[0].stack.length === 1);
+ assert(router.stack[1].path === '/hello/world');
+ assert.deepEqual(router.stack[1].methods, [ 'POST' ]);
+ assert(router.stack[1].stack.length === 1);
+ });
+
+ it('should app.verb(name, url, controller) work', () => {
+ const app = {
+ controller: {
+ async foo() { },
+ hello: {
+ * world() { },
+ },
+ },
+ };
+
+ const router = new EggRouter({}, app);
+ router.get('foo', '/foo', app.controller.foo);
+ router.post('hello', '/hello/world', app.controller.hello.world);
+
+ assert(router.stack[0].name === 'foo');
+ assert(router.stack[0].path === '/foo');
+ assert.deepEqual(router.stack[0].methods, [ 'HEAD', 'GET' ]);
+ assert(router.stack[0].stack.length === 1);
+ assert(router.stack[1].name === 'hello');
+ assert(router.stack[1].path === '/hello/world');
+ assert.deepEqual(router.stack[1].methods, [ 'POST' ]);
+ assert(router.stack[1].stack.length === 1);
+ });
+
+ it('should app.verb(name, url, controllerString) work', () => {
+ const app = {
+ controller: {
+ async foo() { },
+ hello: {
+ * world() { },
+ },
+ },
+ };
+
+ const router = new EggRouter({}, app);
+ router.get('foo', '/foo', 'foo');
+ router.post('hello', '/hello/world', 'hello.world');
+
+ assert(router.stack[0].name === 'foo');
+ assert(router.stack[0].path === '/foo');
+ assert.deepEqual(router.stack[0].methods, [ 'HEAD', 'GET' ]);
+ assert(router.stack[0].stack.length === 1);
+ assert(router.stack[1].name === 'hello');
+ assert(router.stack[1].path === '/hello/world');
+ assert.deepEqual(router.stack[1].methods, [ 'POST' ]);
+ assert(router.stack[1].stack.length === 1);
+ });
+
+ it('should app.verb() throw if not found controller', () => {
+ const app = {
+ controller: {
+ async foo() { },
+ hello: {
+ * world() { },
+ },
+ },
+ };
+
+ const router = new EggRouter({}, app);
+ assert.throws(() => {
+ router.get('foo', '/foo', 'foobar')
+ }, /controller 'foobar' not exists/);
+
+ assert.throws(() => {
+ router.get('/foo', app.bar);
+ }, /controller not exists/);
+ });
+
+ it('should app.verb(name, url, [middlewares], controllerString) work', () => {
+ const app = {
+ controller: {
+ async foo() { },
+ hello: {
+ * world() { },
+ },
+ },
+ };
+
+ const generatorMiddleware = function* () {};
+ const asyncMiddleware = async function() {};
+ const commonMiddleware = function() {};
+
+ const router = new EggRouter({}, app);
+ router.get('foo', '/foo', generatorMiddleware, asyncMiddleware, commonMiddleware, 'foo');
+ router.post('hello', '/hello/world', generatorMiddleware, asyncMiddleware, commonMiddleware, 'hello.world');
+
+ assert(router.stack[0].name === 'foo');
+ assert(router.stack[0].path === '/foo');
+ assert.deepEqual(router.stack[0].methods, [ 'HEAD', 'GET' ]);
+ assert(router.stack[0].stack.length === 4);
+ assert(!is.generatorFunction(router.stack[0].stack[0]));
+ assert(is.asyncFunction(router.stack[0].stack[1]));
+ assert(!is.generatorFunction(router.stack[0].stack[3]));
+ assert(router.stack[1].name === 'hello');
+ assert(router.stack[1].path === '/hello/world');
+ assert.deepEqual(router.stack[1].methods, [ 'POST' ]);
+ assert(router.stack[1].stack.length === 4);
+ assert(!is.generatorFunction(router.stack[1].stack[0]));
+ assert(is.asyncFunction(router.stack[1].stack[1]));
+ assert(!is.generatorFunction(router.stack[1].stack[3]));
+ });
+
+ it('should app.resource() work', () => {
+ const app = {
+ controller: {
+ post: {
+ async index() { },
+ async show() { },
+ async create() { },
+ async update() { },
+ async new() {},
+ },
+ },
+ };
+
+ const asyncMiddleware = async function () { };
+
+ const router = new EggRouter({}, app);
+ router.resources('/post', asyncMiddleware, app.controller.post);
+ assert(router.stack.length === 5);
+ assert(router.stack[0].stack.length === 2);
+
+ router.resources('api_post', '/api/post', app.controller.post);
+ assert(router.stack.length === 10);
+ assert(router.stack[5].stack.length === 1);
+ assert(router.stack[5].name === 'api_posts');
+ });
+
+ it('should router.url work', () => {
+ const app = {
+ controller: {
+ async foo() { },
+ hello: {
+ * world() { },
+ },
+ },
+ };
+ const router = new EggRouter({}, app);
+ router.get('post', '/post/:id', app.controller.foo);
+ router.get('hello', '/hello/world', app.controller.hello.world);
+
+ assert(router.url('post', { id: 1, foo: [1, 2], bar: 'bar' }) === '/post/1?foo=1&foo=2&bar=bar');
+ assert(router.url('post', { foo: [1, 2], bar: 'bar' }) === '/post/:id?foo=1&foo=2&bar=bar');
+ assert(router.url('fooo') === '');
+ assert(router.url('hello') === '/hello/world');
+
+ assert(router.pathFor('post', { id: 1, foo: [1, 2], bar: 'bar' }) === '/post/1?foo=1&foo=2&bar=bar');
+ assert(router.pathFor('fooo') === '');
+ assert(router.pathFor('hello') === '/hello/world');
+ });
+});
diff --git a/test/lib/layer.js b/test/lib/layer.test.js
similarity index 99%
rename from test/lib/layer.js
rename to test/lib/layer.test.js
index 6835254..70ce146 100644
--- a/test/lib/layer.js
+++ b/test/lib/layer.test.js
@@ -9,7 +9,7 @@ var Koa = require('koa')
, should = require('should')
, Layer = require('../../lib/layer');
-describe('Layer', function() {
+describe('test/lib/layer.test.js', function() {
it('composes multiple callbacks/middlware', function(done) {
var app = new Koa();
var router = new Router();
diff --git a/test/lib/router.js b/test/lib/router.test.js
similarity index 99%
rename from test/lib/router.js
rename to test/lib/router.test.js
index 44e7112..ce75958 100644
--- a/test/lib/router.js
+++ b/test/lib/router.test.js
@@ -13,7 +13,7 @@ var fs = require('fs')
, expect = require('expect.js')
, should = require('should');
-describe('Router', function () {
+describe('test/lib/router.test.js', function () {
it('creates new router with koa app', function (done) {
var app = new Koa();
var router = new Router();
@@ -857,12 +857,16 @@ describe('Router', function () {
router.get('/notparameter', function (ctx, next) {
ctx.body = {
param: ctx.params.parameter,
+ routerName: ctx.routerName,
+ routerPath: ctx.routerPath,
};
});
router.get('/:parameter', function (ctx, next) {
ctx.body = {
param: ctx.params.parameter,
+ routerName: ctx.routerName,
+ routerPath: ctx.routerPath,
};
});
@@ -874,6 +878,8 @@ describe('Router', function () {
if (err) return done(err);
expect(res.body.param).to.equal(undefined);
+ expect(res.body.routerName).to.equal(null);
+ expect(res.body.routerPath).to.equal('/notparameter');
done();
});
});
diff --git a/test/lib/utils.test.js b/test/lib/utils.test.js
new file mode 100644
index 0000000..a79be98
--- /dev/null
+++ b/test/lib/utils.test.js
@@ -0,0 +1,59 @@
+'use strict';
+
+const utils = require('../../lib/utils');
+const is = require('is-type-of');
+const assert = require('assert');
+
+describe('test/lib/utils.test.js', () => {
+ describe('callFn', () => {
+ it('should not function return same', () => {
+ const res = utils.callFn('foo');
+ assert(is.promise(res));
+ return res.then(result => assert(result === undefined));
+ });
+
+ it('should async function return promise', () => {
+ const res = utils.callFn(async (foo, bar) => {
+ return foo + bar;
+ }, [ 1, 2 ]);
+ assert(is.promise(res));
+ return res.then(result => assert(result === 3));
+ });
+
+ it('should generator function return promise', () => {
+ const res = utils.callFn(function* (foo, bar) {
+ return foo + bar;
+ }, [ 1, 2 ]);
+ assert(is.promise(res));
+ return res.then(result => assert(result === 3));
+ });
+
+ it('should common function return promise', () => {
+ const res = utils.callFn((foo, bar) => {
+ return foo + bar;
+ }, [ 1, 2 ]);
+ assert(is.promise(res));
+ return res.then(result => assert(result === 3));
+ });
+
+ it('should work with ctx', () => {
+ const res = utils.callFn(async function(bar) {
+ return this.foo + bar;
+ }, [ 2 ], { foo: 1 });
+ assert(is.promise(res));
+ return res.then(result => assert(result === 3));
+ });
+ });
+
+ describe('middleware', () => {
+ it('should work with async function', () => {
+ const res = utils.middleware(async () => {});
+ assert(is.asyncFunction(res));
+ });
+
+ it('should work with generator function', () => {
+ const res = utils.middleware(function* () { });
+ assert(!is.generatorFunction(res));
+ });
+ });
+});