diff --git a/.travis.yml b/.travis.yml
index 33aaef6..d5d0a48 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,8 @@
language: python
install:
- - curl -fsSL https://deno.land/x/install/install.sh | sh -s v0.18.0
+ - curl -fsSL https://deno.land/x/install/install.sh | sh -s v0.19.0
- export PATH="$HOME/.deno/bin:$PATH"
script:
- - deno test/server.js
- - deno test/router.js
- - deno test/toolkit.js
+ - deno test.js
diff --git a/README.md b/README.md
index 65576cc..eadc837 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
Pogo is an easy to use, safe, and expressive framework for writing web servers and applications. It is inspired by [hapi](https://github.com/hapijs/hapi).
-*Supports Deno v0.6.0 and higher.*
+*Supports Deno v0.19.0 and higher.*
## Contents
@@ -44,13 +44,6 @@ Adding routes is easy, just call [`server.route()`](#serverrouteoption) and pass
Add routes in any order you want to, it's safe! Pogo orders them internally by specificity, such that their order of precedence is stable and predictable and avoids ambiguity or conflicts.
-```js
-server.route([
- { method : 'GET', path : '/hi', handler : () => 'Hello!' },
- { method : 'GET', path : '/bye', handler : () => 'Goodbye!' }
-});
-```
-
```js
server.route({ method : 'GET', path : '/hi', handler : () => 'Hello!' });
server.route({ method : 'GET', path : '/bye', handler : () => 'Goodbye!' });
@@ -62,6 +55,13 @@ server
.route({ method : 'GET', path : '/bye', handler : () => 'Goodbye!' });
```
+```js
+server.route([
+ { method : 'GET', path : '/hi', handler : () => 'Hello!' },
+ { method : 'GET', path : '/bye', handler : () => 'Goodbye!' }
+});
+```
+
You can also configure the route to handle multiple methods by using an array, or `*` to handle all possible methods.
```js
@@ -71,7 +71,7 @@ server.route({ method : ['GET', 'POST'], path : '/hi', handler : () => 'Hello!'
server.route({ method : '*', path : '/hi', handler : () => 'Hello!' });
```
-### Writing Tests
+### Writing tests
When it comes time to write tests for your app, Pogo has you covered with [`server.inject()`](serverinjectrequest).
@@ -88,8 +88,8 @@ const response = await server.inject({
## API
+ - [`server = pogo.server(option)`](#server--pogoserveroption)
- [Server](#server)
- - [`server = pogo.server(option)`](#server--pogoserveroption)
- [`server.inject(request)`](#serverinjectrequest)
- [`server.route(option)`](#serverrouteoption)
- [`server.start()`](#serverstart)
@@ -97,8 +97,18 @@ const response = await server.inject({
- [`request.body()`](#requestbody)
- [`request.bodyStream()`](#requestbodystream)
- [`request.headers`](#requestheaders)
+ - [`request.host`](#requesthost)
+ - [`request.hostname`](#requesthostname)
+ - [`request.href`](#requesthref)
- [`request.method`](#requestmethod)
+ - [`request.origin`](#requestorigin)
- [`request.params`](#requestparams)
+ - [`request.path`](#requestpath)
+ - [`request.raw`](#requestraw)
+ - [`request.response`](#requestresponse)
+ - [`request.search`](#requestsearch)
+ - [`request.searchParams`](#requestsearchparams)
+ - [`request.server`](#requestserver)
- [`request.url`](#requesturl)
- [Response](#response)
- [`response.body`](#responsebody)
@@ -114,32 +124,34 @@ const response = await server.inject({
- [`response.temporary()`](#responsetemporary)
- [`response.type(mediaType)`](#responsetypemediatype)
- [Response Toolkit](#response-toolkit)
- - [`h.response(body)`](#hresponsebody)
- [`h.redirect(url)`](#hredirecturl)
+ - [`h.response(body)`](#hresponsebody)
-### Server
-
-#### server = pogo.server(option)
+### server = pogo.server(option)
Returns a server instance, which can then be used to add routes and start listening for requests.
-##### option
+#### option
Type: `object`
-###### hostname
+##### hostname
Type: `string`
Default: `'localhost'`
Specifies which domain or IP address the server will listen on when `server.start()` is called. Use `0.0.0.0` to listen on any hostname.
-###### port
+##### port
Type: `number`
Example: `3000`
-Specifies which port number the server will listen on when `server.start()` is called. Use `0` to listen on any available port.
+Specifies which port number the server will listen on when [`server.start()`](#serverstart) is called. Use `0` to listen on any available port.
+
+### Server
+
+The `server` object returned by `pogo.server()` represents your [web server](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_web_server). When you start the server, it begins listening for HTTP requests, processes those requests when they are received, and makes the content within each request available to the route handlers that you specify.
#### server.inject(request)
@@ -165,11 +177,11 @@ Adds a route to the server so that the server knows how to respond to requests f
##### option
-Type: `object` or `array`
+Type: `object` or `array`
###### method
-Type: `string`
+Type: `string` or `array`
Example: `GET`
Any valid [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods), or `*` to match all methods. Used to limit which requests will trigger the route handler.
@@ -181,7 +193,7 @@ Example: `'/users/{userId}'`
Any valid URL path. Used to limit which requests will trigger the route handler.
-Supports path parameters with dynamic values, which can be accessed in the handler as `request.params`.
+Supports path parameters with dynamic values, which can be accessed in the handler as [`request.params`](#requestparams).
###### handler(request, h)
@@ -232,25 +244,104 @@ Type: [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers)
Contains the [HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) that were sent in the request, such as [`Accept`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept), [`User-Agent`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent), and others.
+#### request.host
+
+Type: `string`
+Example: `'localhost:3000'`
+
+The value of the HTTP [`Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) header, which is a combination of the hostname and port at which the server received the request, separated by a `:` colon. Useful for returning different content depending on which URL your visitors use to access the server. Shortcut for `request.url.host`.
+
+To get the hostname, which does not include the port number, see [`request.hostname`](#requesthostname).
+
+#### request.hostname
+
+Type: `string`
+Example: `'localhost'`
+
+The hostname part of the HTTP [`Host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Host) header. That is, the domain or IP address at which the server received the request, without the port number. Useful for returning different content depending on which URL your visitors use to access the server. Shortcut for `request.url.hostname`.
+
+To get the host, which includes the port number, see [`request.host`](#requesthostname).
+
+#### request.href
+
+Type: `string`
+Example: `'http://localhost:3000/page.html?query'`
+
+The full URL associated with the request, represented as a string. Shortcut for `request.url.href`.
+
+To get this value as a parsed object instead, use [`request.url`](#requesturl).
+
#### request.method
-Type: `string` or `array`
+Type: `string`
Example: `'GET'`
The [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) associated with the request, such as [`GET`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) or [`POST`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST).
+#### request.origin
+
+Type: `string`
+Example: `'http://localhost:3000'`
+
+The scheme and host parts of the request URL. Shortcut for `request.url.origin`.
+
#### request.params
Type: `object`
Contains the dynamic variables of the `path` in the route configuration, where each key is a variable name and the value is the corresponding part of the request path.
-#### request.url
+#### request.path
+
+Type: `string`
+Example: `/page.html`
+
+The path part of the request URL, excluding the query. Shortcut for `request.url.pathname`.
+
+#### request.raw
+
+Type: [`ServerRequest`](https://github.com/denoland/deno_std/blob/5d0dd5878e82ab7577356096469a7e280efe8442/http/server.ts#L100-L202)
+
+The original request object from Deno's `http` module, upon which many of the other request properties are based.
+
+*You probably don't need this. It is provided as an escape hatch, but using it is not recommended.*
+
+#### request.response
+
+Type: [`Response`](#response)
+
+The response that will be sent for the request. To create a new response, see [`h.response()`](#hresponsebody).
+
+#### request.search
Type: `string`
-Example: `'/users/123'`
+Example: `'?query'`
+
+The query part of the request URL, represented as a string. Shortcut for `request.url.search`.
+
+To get this value as a parsed object instead, use [`request.searchParams`](#requestsearchparams).
+
+#### request.searchParams
+
+Type: [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
+
+The query part of the request URL, represented as an object that has methods for working with the query parameters. Shortcut for `request.url.searchParams`.
+
+To get this value as a string instead, use [`request.search`](#requestsearch).
-The URL path associated with the request,
+#### request.server
+
+Type: [`Server`](#server)
+
+The server that is handling the request.
+
+#### request.url
+
+Type: [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL)
+
+The full URL associated with the request, represented as an object that contains properties for various parts of the URL,
+
+To get this value as a string instead, use [`request.href`](#requesthref). In some cases, the URL object itself can be used as if it were a string, because it has a smart `.toString()` method.
### Response
@@ -329,9 +420,10 @@ Returns the response so other methods can be chained.
#### response.status
-Type: `number`
+Type: `number`
+Example: [`418`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/418)
-The [status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) that will be sent in the response. Defaults to [`200`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200), which means the request succeeded.
+The [status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) that will be sent in the response. Defaults to [`200`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200), which means the request succeeded. 4xx and 5xx codes indicate an error.
#### response.temporary()
diff --git a/dependencies.js b/dependencies.js
index ac22314..7514635 100644
--- a/dependencies.js
+++ b/dependencies.js
@@ -1,5 +1,8 @@
-import * as http from "https://deno.land/std@v0.18.0/http/server.ts";
-import { Status as status } from "https://deno.land/std@v0.18.0/http/http_status.ts";
-import { STATUS_TEXT as statusText } from "https://deno.land/std@v0.18.0/http/http_status.ts";
+import * as http from 'https://deno.land/std@v0.19.0/http/server.ts';
+import { Status as status, STATUS_TEXT as statusText } from 'https://deno.land/std@v0.19.0/http/http_status.ts';
-export { http, status, statusText };
+export {
+ http,
+ status,
+ statusText
+};
diff --git a/dev-dependencies.js b/dev-dependencies.js
new file mode 100644
index 0000000..fe37c4c
--- /dev/null
+++ b/dev-dependencies.js
@@ -0,0 +1,9 @@
+import { assertEquals, assertStrictEq } from 'https://deno.land/std@v0.19.0/testing/asserts.ts';
+import { runTests, test } from 'https://deno.land/std@v0.19.0/testing/mod.ts';
+
+export {
+ assertEquals,
+ assertStrictEq,
+ runTests,
+ test
+};
diff --git a/lib/request.js b/lib/request.js
new file mode 100644
index 0000000..0c293c5
--- /dev/null
+++ b/lib/request.js
@@ -0,0 +1,41 @@
+import Response from './response.js';
+
+export default class Request {
+ constructor(option) {
+ this.raw = option.raw;
+ this.route = option.route;
+ this.method = this.raw.method;
+ this.headers = this.raw.headers || new Headers({ host : 'localhost' });
+ this.params = this.route.params;
+ this.response = new Response();
+ this.server = option.server;
+ this.url = new URL(this.raw.url, 'http://' + this.headers.get('host'));
+ }
+ body(...args) {
+ return this.raw.body(...args);
+ }
+ bodyStream(...args) {
+ return this.raw.bodyStream(...args);
+ }
+ get host() {
+ return this.url.host;
+ }
+ get hostname() {
+ return this.url.hostname;
+ }
+ get href() {
+ return this.url.href;
+ }
+ get origin() {
+ return this.url.origin;
+ }
+ get path() {
+ return this.url.pathname;
+ }
+ get search() {
+ return this.url.search;
+ }
+ get searchParams() {
+ return this.url.searchParams;
+ }
+}
diff --git a/lib/respond.js b/lib/respond.js
index 205bdea..ae0071c 100644
--- a/lib/respond.js
+++ b/lib/respond.js
@@ -35,19 +35,17 @@ const respond = (request, source) => {
respond.notFound = (request) => {
const response = new Response({
- error : statusText.get(status.NotFound),
- message : 'Page not found'
- })
- .code(status.NotFound);
+ error : statusText.get(status.NotFound),
+ message : 'Page not found'
+ }).code(status.NotFound);
return respond(request, response);
};
respond.badImplementation = (request) => {
const response = new Response({
- error : statusText.get(status.InternalServerError),
- message : 'An internal error occurred on the server'
- })
- .code(status.InternalServerError);
+ error : statusText.get(status.InternalServerError),
+ message : 'An internal error occurred on the server'
+ }).code(status.InternalServerError);
return respond(request, response);
};
diff --git a/lib/router.js b/lib/router.js
index fc03e4a..ed93ab0 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -6,33 +6,33 @@ const getParamName = (segment) => {
return segment.slice(1, -1);
};
-const sortRoutes = (a, b) => {
- const aFirst = -1;
- const bFirst = 1;
+const sortRoutes = (left, right) => {
+ const leftFirst = -1;
+ const rightFirst = 1;
- if (a.segments.filter(isDynamicSegment).length <
- b.segments.filter(isDynamicSegment).length) {
- return aFirst;
+ if (left.segments.filter(isDynamicSegment).length <
+ right.segments.filter(isDynamicSegment).length) {
+ return leftFirst;
}
- if (a.segments.filter(isDynamicSegment).length >
- b.segments.filter(isDynamicSegment).length) {
- return bFirst;
+ if (left.segments.filter(isDynamicSegment).length >
+ right.segments.filter(isDynamicSegment).length) {
+ return rightFirst;
}
- if (a.segments.length < b.segments.length) {
- return aFirst;
+ if (left.segments.length < right.segments.length) {
+ return leftFirst;
}
- if (a.segments.length > b.segments.length) {
- return bFirst;
+ if (left.segments.length > right.segments.length) {
+ return rightFirst;
}
- if (a.path.length < b.path.length) {
- return aFirst;
+ if (left.path.length < right.path.length) {
+ return leftFirst;
}
- if (a.path.length > b.path.length) {
- return bFirst;
+ if (left.path.length > right.path.length) {
+ return rightFirst;
}
-}
+};
class Router {
constructor() {
@@ -40,7 +40,10 @@ class Router {
}
add(option, data) {
const method = option.method.toLowerCase();
- this.routes[method] = this.routes[method] || { static : new Map(), dynamic : [] };
+ this.routes[method] = this.routes[method] || {
+ static : new Map(),
+ dynamic : []
+ };
const table = this.routes[method];
const segments = option.path.split('/').filter(Boolean);
const isDynamic = segments.some(isDynamicSegment);
@@ -64,13 +67,13 @@ class Router {
return {
data : methodTable.static.get(path).data,
params : {}
- }
+ };
}
if (wildTable && wildTable.static.has(path)) {
return {
data : wildTable.static.get(path).data,
params : {}
- }
+ };
}
const segments = path.split('/').filter(Boolean);
const methodDynamic = (methodTable || {}).dynamic || [];
diff --git a/lib/server.js b/lib/server.js
new file mode 100644
index 0000000..b972a49
--- /dev/null
+++ b/lib/server.js
@@ -0,0 +1,72 @@
+import { http } from '../dependencies.js';
+import respond from './respond.js';
+import Request from './request.js';
+import Toolkit from './toolkit.js';
+import Router from './router.js';
+
+const getPathname = (path) => {
+ return new URL(path, 'http://localhost').pathname;
+};
+
+export default class Server {
+ constructor(option) {
+ this._config = {
+ hostname : 'localhost',
+ ...option
+ };
+ this.router = new Router();
+ }
+ async inject(rawRequest) {
+ const route = this.router.route(rawRequest.method, getPathname(rawRequest.url));
+ const handler = route && route.data;
+
+ if (!handler) {
+ return respond.notFound();
+ }
+
+ const request = new Request({
+ raw : rawRequest,
+ route,
+ server : this
+ });
+
+ let result;
+ try {
+ result = await handler(request, new Toolkit());
+ }
+ catch (error) {
+ return respond.badImplementation();
+ }
+
+ return respond(request, result);
+ }
+ async respond(request) {
+ const response = await this.inject(request);
+ request.respond(response);
+ }
+ route(routes) {
+ for (const route of [].concat(routes)) {
+ if (typeof route.handler !== 'function') {
+ throw new TypeError('route.handler must be a function');
+ }
+ for (const method of [].concat(route.method)) {
+ const record = {
+ ...route,
+ method
+ };
+ this.router.add(record, route.handler);
+ }
+ }
+ return this;
+ }
+ async start() {
+ const host = this._config.hostname + ':' + this._config.port;
+ const server = http.serve(host);
+ setTimeout(() => {
+ server.close();
+ }, 10000);
+ for await (const request of server) {
+ this.respond(request);
+ }
+ }
+}
diff --git a/main.js b/main.js
index b58f934..6b36284 100644
--- a/main.js
+++ b/main.js
@@ -1,66 +1,7 @@
-import { http } from './dependencies.js';
-import respond from './lib/respond.js';
-import Response from './lib/response.js';
-import Toolkit from './lib/toolkit.js';
-import Router from './lib/router.js';
-
-class Pogo {
- constructor(option) {
- this._config = {
- hostname : 'localhost',
- ...option
- };
- this.router = new Router();
- }
- async inject(request) {
- console.log(`Request: ${new Date().toISOString()} ${request.method} ${request.url}`);
-
- const route = this.router.route(request.method, request.url);
- const handler = route && route.data;
-
- if (!handler) {
- return respond.notFound(request);
- }
-
- request.params = route.params;
- request.response = new Response();
-
- let result;
- try {
- result = await handler(request, new Toolkit());
- }
- catch (error) {
- return respond.badImplementation(request);
- }
-
- return respond(request, result);
- }
- async respond(request) {
- const response = await this.inject(request);
- request.respond(response);
- }
- route(routes) {
- for (const route of [].concat(routes)) {
- if (typeof route.handler !== 'function') {
- throw new TypeError('route.handler must be a function');
- }
- for (const method of [].concat(route.method)) {
- this.router.add({ ...route, method }, route.handler);
- }
- }
- return this;
- }
- async start() {
- const host = this._config.hostname + ':' + this._config.port;
- const server = http.serve(host);
- for await (const request of server) {
- this.respond(request);
- }
- }
-}
+import Server from './lib/server.js';
export default {
server(...args) {
- return new Pogo(...args);
+ return new Server(...args);
}
};
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..dfd54bd
--- /dev/null
+++ b/test.js
@@ -0,0 +1,7 @@
+import './test/request.js';
+import './test/router.js';
+import './test/server.js';
+import './test/toolkit.js';
+import { runTests } from './dev-dependencies.js';
+
+runTests();
diff --git a/test/request.js b/test/request.js
new file mode 100644
index 0000000..527ce63
--- /dev/null
+++ b/test/request.js
@@ -0,0 +1,398 @@
+import { assertEquals, assertStrictEq, test } from '../dev-dependencies.js';
+import Response from '../lib/response.js';
+import Server from '../lib/server.js';
+import pogo from '../main.js';
+
+const encoder = new TextEncoder();
+
+test('request.headers is a Headers instance', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isHeadersInstance : request.headers instanceof Headers,
+ hostHeader : request.headers.get('host'),
+ type : typeof request.headers
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isHeadersInstance : true,
+ hostHeader : 'localhost',
+ type : 'object'
+ })));
+});
+
+test('request.host is a hostname and port', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isSameAsHeader : request.host === request.headers.get('host'),
+ isSameAsUrl : request.host === request.url.host,
+ type : typeof request.host,
+ value : request.host
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isSameAsHeader : true,
+ isSameAsUrl : true,
+ type : 'string',
+ value : 'localhost'
+ })));
+});
+
+test('request.hostname is a domain or IP address', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isInHostHeader : request.hostname === request.headers.get('host').split(':')[0],
+ isSameAsUrl : request.hostname === request.url.hostname,
+ type : typeof request.hostname,
+ value : request.hostname
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isInHostHeader : true,
+ isSameAsUrl : true,
+ type : 'string',
+ value : 'localhost'
+ })));
+});
+
+test('request.href is a full URL string', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isSameAsUrl : request.href === request.url.href,
+ type : typeof request.href,
+ value : request.href
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isSameAsUrl : true,
+ type : 'string',
+ value : 'http://localhost/'
+ })));
+});
+
+test('request.method is an HTTP method', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isGet : request.method === 'GET',
+ type : typeof request.method
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isGet : true,
+ type : 'string'
+ })));
+});
+
+test('request.origin is a protocol and host', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isSameAsUrl : request.origin === request.url.origin,
+ type : typeof request.origin,
+ value : request.origin
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isSameAsUrl : true,
+ type : 'string',
+ value : 'http://localhost'
+ })));
+});
+
+test('request.params contains path variables', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/users/{userId}',
+ handler(request) {
+ return {
+ type : typeof request.params,
+ userId : request.params.userId,
+ value : request.params
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/users/123'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ type : 'object',
+ userId : '123',
+ value : {
+ userId : '123'
+ }
+ })));
+});
+
+test('request.path is a URL path string', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isSameAsUrl : request.path === request.url.pathname,
+ type : typeof request.path,
+ value : request.path
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/?query'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isSameAsUrl : true,
+ type : 'string',
+ value : '/'
+ })));
+});
+
+test('request.raw is the original request', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isGet : request.raw.method === 'GET',
+ type : typeof request.raw
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isGet : true,
+ type : 'object'
+ })));
+});
+
+test('request.response is a Response instance', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isResponseInstance : request.response instanceof Response,
+ type : typeof request.response
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isResponseInstance : true,
+ type : 'object'
+ })));
+});
+
+test('request.route is a router record', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ hasHandler : typeof request.route.data === 'function',
+ params : request.route.params,
+ type : typeof request.route
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ hasHandler : true,
+ params : {},
+ type : 'object'
+ })));
+});
+
+test('request.search is a URL search string', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isSameAsUrl : request.search === request.url.search,
+ type : typeof request.search,
+ value : request.search
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/?query'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isSameAsUrl : true,
+ type : 'string',
+ value : '?query'
+ })));
+});
+
+test('request.searchParams is a URLSearchParams instance', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ asString : request.searchParams.toString(),
+ isSameAsUrl : request.searchParams === request.url.searchParams,
+ isParamsInstance : request.searchParams instanceof URLSearchParams,
+ type : typeof request.searchParams
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/?query'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ asString : 'query=',
+ isSameAsUrl : true,
+ isParamsInstance : true,
+ type : 'object'
+ })));
+});
+
+test('request.server is a Server instance', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ isServerInstance : request.server instanceof Server,
+ type : typeof request.server
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ isServerInstance : true,
+ type : 'object'
+ })));
+});
+
+test('request.url is a URL instance', async () => {
+ const server = pogo.server();
+ server.route({
+ method : 'GET',
+ path : '/',
+ handler(request) {
+ return {
+ asString : request.url,
+ href : request.url.href,
+ isUrlInstance : request.url instanceof URL,
+ type : typeof request.url
+ };
+ }
+ });
+ const response = await server.inject({
+ method : 'GET',
+ url : '/'
+ });
+ assertStrictEq(response.status, 200);
+ assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(response.body, encoder.encode(JSON.stringify({
+ asString : 'http://localhost/',
+ href : 'http://localhost/',
+ isUrlInstance : true,
+ type : 'object'
+ })));
+});
diff --git a/test/router.js b/test/router.js
index f89d63b..4f92e41 100644
--- a/test/router.js
+++ b/test/router.js
@@ -1,6 +1,5 @@
+import { assertEquals, test } from '../dev-dependencies.js';
import Router from '../lib/router.js';
-import { assertEquals } from 'https://deno.land/std@v0.18.0/testing/asserts.ts';
-import { runTests, test } from 'https://deno.land/std@v0.18.0/testing/mod.ts';
test('add() static routes', () => {
const router = new Router();
@@ -83,5 +82,3 @@ test('route() wildcard method routes', () => {
}
});
});
-
-runTests();
diff --git a/test/server.js b/test/server.js
index ff05c42..6771577 100644
--- a/test/server.js
+++ b/test/server.js
@@ -1,5 +1,4 @@
-import { assertEquals, assertStrictEq } from 'https://deno.land/std@v0.18.0/testing/asserts.ts';
-import { runTests, test } from 'https://deno.land/std@v0.18.0/testing/mod.ts';
+import { assertEquals, assertStrictEq, test } from '../dev-dependencies.js';
import pogo from '../main.js';
const encoder = new TextEncoder();
@@ -20,6 +19,7 @@ test('HTML response for string', async () => {
url : '/'
});
assertStrictEq(called, true);
+ assertStrictEq(response.status, 200);
assertStrictEq(response.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(response.body, encoder.encode('hi'));
});
@@ -40,6 +40,7 @@ test('JSON response for plain object', async () => {
url : '/'
});
assertStrictEq(called, true);
+ assertStrictEq(response.status, 200);
assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
assertEquals(response.body, encoder.encode(JSON.stringify({ foo : 'bar' })));
});
@@ -68,8 +69,10 @@ test('JSON response for boolean', async () => {
method : 'GET',
url : '/true'
});
+ assertStrictEq(responseFalse.status, 200);
assertStrictEq(responseFalse.headers.get('content-type'), 'application/json; charset=utf-8');
assertEquals(responseFalse.body, encoder.encode(JSON.stringify(false)));
+ assertStrictEq(responseTrue.status, 200);
assertStrictEq(responseTrue.headers.get('content-type'), 'application/json; charset=utf-8');
assertEquals(responseTrue.body, encoder.encode(JSON.stringify(true)));
});
@@ -98,8 +101,10 @@ test('JSON response for number', async () => {
method : 'GET',
url : '/one'
});
+ assertStrictEq(responseZero.status, 200);
assertStrictEq(responseZero.headers.get('content-type'), 'application/json; charset=utf-8');
assertEquals(responseZero.body, encoder.encode(JSON.stringify(0)));
+ assertStrictEq(responseOne.status, 200);
assertStrictEq(responseOne.headers.get('content-type'), 'application/json; charset=utf-8');
assertEquals(responseOne.body, encoder.encode(JSON.stringify(1)));
});
@@ -117,6 +122,7 @@ test('empty response for null', async () => {
method : 'GET',
url : '/'
});
+ assertStrictEq(response.status, 200);
assertStrictEq(response.headers.has('content-type'), false);
assertEquals(response.body, encoder.encode(''));
});
@@ -155,6 +161,7 @@ test('route with dynamic path', async () => {
method : 'GET',
url : '/users/123'
});
+ assertStrictEq(response.status, 200);
assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
assertEquals(response.body, encoder.encode(JSON.stringify({ userId : '123' })));
});
@@ -184,8 +191,10 @@ test('server.route() can be chained', async () => {
method : 'GET',
url : '/b'
});
+ assertStrictEq(responseA.status, 200);
assertStrictEq(responseA.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(responseA.body, encoder.encode('a'));
+ assertStrictEq(responseB.status, 200);
assertStrictEq(responseB.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(responseB.body, encoder.encode('b'));
});
@@ -216,8 +225,10 @@ test('array of routes', async () => {
method : 'GET',
url : '/b'
});
+ assertStrictEq(responseA.status, 200);
assertStrictEq(responseA.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(responseA.body, encoder.encode('a'));
+ assertStrictEq(responseB.status, 200);
assertStrictEq(responseB.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(responseB.body, encoder.encode('b'));
});
@@ -243,12 +254,18 @@ test('route with array of methods', async () => {
method : 'PUT',
url : '/hello'
});
+ assertStrictEq(getResponse.status, 200);
assertStrictEq(getResponse.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(getResponse.body, encoder.encode('Hi, GET'));
+ assertStrictEq(postResponse.status, 200);
assertStrictEq(postResponse.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(postResponse.body, encoder.encode('Hi, POST'));
assertStrictEq(putResponse.status, 404);
assertStrictEq(putResponse.headers.get('content-type'), 'application/json; charset=utf-8');
+ assertEquals(putResponse.body, encoder.encode(JSON.stringify({
+ error : 'Not Found',
+ message : 'Page not found'
+ })));
});
test('route with wildcard method', async () => {
@@ -272,12 +289,13 @@ test('route with wildcard method', async () => {
method : 'PUT',
url : '/hello'
});
+ assertStrictEq(getResponse.status, 200);
assertStrictEq(getResponse.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(getResponse.body, encoder.encode('Hi, GET'));
+ assertStrictEq(postResponse.status, 200);
assertStrictEq(postResponse.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(postResponse.body, encoder.encode('Hi, POST'));
+ assertStrictEq(putResponse.status, 200);
assertStrictEq(putResponse.headers.get('content-type'), 'text/html; charset=utf-8');
assertEquals(putResponse.body, encoder.encode('Hi, PUT'));
});
-
-runTests();
diff --git a/test/toolkit.js b/test/toolkit.js
index 6c6824f..bf63b20 100644
--- a/test/toolkit.js
+++ b/test/toolkit.js
@@ -1,5 +1,4 @@
-import { assertEquals, assertStrictEq } from 'https://deno.land/std@v0.18.0/testing/asserts.ts';
-import { runTests, test } from 'https://deno.land/std@v0.18.0/testing/mod.ts';
+import { assertEquals, assertStrictEq, test } from '../dev-dependencies.js';
import pogo from '../main.js';
const encoder = new TextEncoder();
@@ -17,6 +16,7 @@ test('h.response() set JSON body', async () => {
method : 'GET',
url : '/'
});
+ assertStrictEq(response.status, 200);
assertStrictEq(response.headers.get('content-type'), 'application/json; charset=utf-8');
assertEquals(response.body, encoder.encode(JSON.stringify({ hello : 'world' })));
});
@@ -86,6 +86,7 @@ test('response.header() set custom header', async () => {
method : 'GET',
url : '/'
});
+ assertStrictEq(response.status, 200);
assertStrictEq(response.headers.get('content-type'), 'text/html; charset=utf-8');
assertStrictEq(response.headers.get('x-dog'), 'woof');
assertEquals(response.body, encoder.encode('hi'));
@@ -104,6 +105,7 @@ test('response.location() set location header', async () => {
method : 'GET',
url : '/'
});
+ assertStrictEq(response.status, 200);
assertStrictEq(response.headers.get('content-type'), 'text/html; charset=utf-8');
assertStrictEq(response.headers.get('location'), '/over-the-rainbow');
assertEquals(response.body, encoder.encode('hi'));
@@ -190,5 +192,3 @@ test('response.type() override default content-type handling', async () => {
assertStrictEq(response.headers.get('content-type'), 'weird/type');
assertEquals(response.body, encoder.encode(JSON.stringify({ hello : 'world' })));
});
-
-runTests();