@e22m4u/js-http-static-router 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.json +2 -1
- package/README.md +265 -17
- package/build-cjs.js +1 -1
- package/dist/cjs/index.cjs +114 -76
- package/example/server.js +23 -13
- package/mocha.setup.js +4 -0
- package/package.json +10 -8
- package/src/http-static-router.d.ts +7 -2
- package/src/http-static-router.js +73 -61
- package/src/http-static-router.spec.js +899 -0
- package/src/static-route.js +18 -8
- package/src/types.d.ts +7 -0
- package/src/utils/create-cookie-string.d.ts +6 -0
- package/src/utils/create-cookie-string.js +28 -0
- package/src/utils/create-cookie-string.spec.js +32 -0
- package/src/utils/create-error.d.ts +14 -0
- package/src/utils/create-error.js +29 -0
- package/src/utils/create-error.spec.js +42 -0
- package/src/utils/create-request-mock.d.ts +35 -0
- package/src/utils/create-request-mock.js +483 -0
- package/src/utils/create-request-mock.spec.js +646 -0
- package/src/utils/create-response-mock.d.ts +18 -0
- package/src/utils/create-response-mock.js +131 -0
- package/src/utils/create-response-mock.spec.js +150 -0
- package/src/utils/fetch-request-body.d.ts +26 -0
- package/src/utils/fetch-request-body.js +148 -0
- package/src/utils/fetch-request-body.spec.js +209 -0
- package/src/utils/get-pathname-from-url.d.ts +1 -1
- package/src/utils/{get-request-pathname.spec.js → get-pathname-from-url.spec.js} +1 -1
- package/src/utils/index.d.ts +8 -0
- package/src/utils/index.js +8 -0
- package/src/utils/is-readable-stream.d.ts +9 -0
- package/src/utils/is-readable-stream.js +12 -0
- package/src/utils/is-readable-stream.spec.js +23 -0
- package/src/utils/parse-content-type.d.ts +15 -0
- package/src/utils/parse-content-type.js +34 -0
- package/src/utils/parse-content-type.spec.js +79 -0
- package/src/utils/parse-cookie-string.d.ts +19 -0
- package/src/utils/parse-cookie-string.js +36 -0
- package/src/utils/parse-cookie-string.spec.js +45 -0
- package/{example/static → static}/index.html +2 -2
- /package/{example/static/nested/file.txt → static/assets/nested/heart.txt} +0 -0
- /package/{example/static/file.txt → static/assets/rabbit.txt} +0 -0
package/src/utils/index.js
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
export * from './create-error.js';
|
|
1
2
|
export * from './escape-regexp.js';
|
|
3
|
+
export * from './fetch-request-body.js';
|
|
4
|
+
export * from './is-readable-stream.js';
|
|
5
|
+
export * from './parse-content-type.js';
|
|
6
|
+
export * from './create-request-mock.js';
|
|
7
|
+
export * from './parse-cookie-string.js';
|
|
8
|
+
export * from './create-response-mock.js';
|
|
9
|
+
export * from './create-cookie-string.js';
|
|
2
10
|
export * from './get-pathname-from-url.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check whether a value has a pipe method.
|
|
3
|
+
*
|
|
4
|
+
* @param {*} value
|
|
5
|
+
* @returns {boolean}
|
|
6
|
+
*/
|
|
7
|
+
export function isReadableStream(value) {
|
|
8
|
+
if (!value || typeof value !== 'object') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return typeof value.pipe === 'function';
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {expect} from 'chai';
|
|
2
|
+
import {Readable} from 'stream';
|
|
3
|
+
import {isReadableStream} from './is-readable-stream.js';
|
|
4
|
+
|
|
5
|
+
describe('isReadableStream', function () {
|
|
6
|
+
it('should return true when the value is a readable stream', function () {
|
|
7
|
+
const value1 = new Readable();
|
|
8
|
+
expect(isReadableStream(value1)).to.be.true;
|
|
9
|
+
const value2 = {pipe: () => undefined};
|
|
10
|
+
expect(isReadableStream(value2)).to.be.true;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should return false when the value is not a stream', function () {
|
|
14
|
+
expect(isReadableStream('string')).to.be.false;
|
|
15
|
+
expect(isReadableStream(5)).to.be.false;
|
|
16
|
+
expect(isReadableStream([])).to.be.false;
|
|
17
|
+
expect(isReadableStream({})).to.be.false;
|
|
18
|
+
expect(isReadableStream(undefined)).to.be.false;
|
|
19
|
+
expect(isReadableStream(null)).to.be.false;
|
|
20
|
+
expect(isReadableStream(NaN)).to.be.false;
|
|
21
|
+
expect(isReadableStream(() => 10)).to.be.false;
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed content type.
|
|
3
|
+
*/
|
|
4
|
+
export type ParsedContentType = {
|
|
5
|
+
boundary: string | undefined;
|
|
6
|
+
charset: string | undefined;
|
|
7
|
+
mediaType: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse content type.
|
|
12
|
+
*
|
|
13
|
+
* @param input
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseContentType(input: string): ParsedContentType;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {InvalidArgumentError} from '@e22m4u/js-format';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse content type.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} input
|
|
7
|
+
* @returns {{
|
|
8
|
+
* boundary: string|undefined,
|
|
9
|
+
* charset: string|undefined,
|
|
10
|
+
* mediaType: string|undefined,
|
|
11
|
+
* }}
|
|
12
|
+
*/
|
|
13
|
+
export function parseContentType(input) {
|
|
14
|
+
if (typeof input !== 'string') {
|
|
15
|
+
throw new InvalidArgumentError(
|
|
16
|
+
'Parameter "input" must be a String, but %v was given.',
|
|
17
|
+
input,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const res = {mediaType: undefined, charset: undefined, boundary: undefined};
|
|
21
|
+
const re =
|
|
22
|
+
/^\s*([^\s;/]+\/[^\s;/]+)(?:;\s*charset=([^\s;]+))?(?:;\s*boundary=([^\s;]+))?.*$/i;
|
|
23
|
+
const matches = re.exec(input);
|
|
24
|
+
if (matches && matches[1]) {
|
|
25
|
+
res.mediaType = matches[1];
|
|
26
|
+
if (matches[2]) {
|
|
27
|
+
res.charset = matches[2];
|
|
28
|
+
}
|
|
29
|
+
if (matches[3]) {
|
|
30
|
+
res.boundary = matches[3];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return res;
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {expect} from 'chai';
|
|
2
|
+
import {format} from '@e22m4u/js-format';
|
|
3
|
+
import {parseContentType} from './parse-content-type.js';
|
|
4
|
+
|
|
5
|
+
describe('parseContentType', function () {
|
|
6
|
+
it('should require the parameter "input" to be a String', function () {
|
|
7
|
+
const throwable = v => () => parseContentType(v);
|
|
8
|
+
const error = s =>
|
|
9
|
+
format('Parameter "input" must be a String, but %s was given.', s);
|
|
10
|
+
expect(throwable(10)).to.throw(error('10'));
|
|
11
|
+
expect(throwable(0)).to.throw(error('0'));
|
|
12
|
+
expect(throwable(true)).to.throw(error('true'));
|
|
13
|
+
expect(throwable(false)).to.throw(error('false'));
|
|
14
|
+
expect(throwable([])).to.throw(error('Array'));
|
|
15
|
+
expect(throwable({})).to.throw(error('Object'));
|
|
16
|
+
expect(throwable(undefined)).to.throw(error('undefined'));
|
|
17
|
+
expect(throwable(null)).to.throw(error('null'));
|
|
18
|
+
expect(throwable(() => undefined)).to.throw(error('Function'));
|
|
19
|
+
throwable('text/html')();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return the object with specific properties', function () {
|
|
23
|
+
const res = parseContentType('');
|
|
24
|
+
expect(res).to.be.eql({
|
|
25
|
+
mediaType: undefined,
|
|
26
|
+
charset: undefined,
|
|
27
|
+
boundary: undefined,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should parse the media type from the given string', function () {
|
|
32
|
+
const res1 = parseContentType('text/html');
|
|
33
|
+
expect(res1).to.be.eql({
|
|
34
|
+
mediaType: 'text/html',
|
|
35
|
+
charset: undefined,
|
|
36
|
+
boundary: undefined,
|
|
37
|
+
});
|
|
38
|
+
const res2 = parseContentType('text/html;');
|
|
39
|
+
expect(res2).to.be.eql({
|
|
40
|
+
mediaType: 'text/html',
|
|
41
|
+
charset: undefined,
|
|
42
|
+
boundary: undefined,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should parse the media type with charset', function () {
|
|
47
|
+
const res1 = parseContentType('text/html; charset=utf-8');
|
|
48
|
+
expect(res1).to.be.eql({
|
|
49
|
+
mediaType: 'text/html',
|
|
50
|
+
charset: 'utf-8',
|
|
51
|
+
boundary: undefined,
|
|
52
|
+
});
|
|
53
|
+
const res2 = parseContentType('text/html; charset=utf-8;');
|
|
54
|
+
expect(res2).to.be.eql({
|
|
55
|
+
mediaType: 'text/html',
|
|
56
|
+
charset: 'utf-8',
|
|
57
|
+
boundary: undefined,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should parse the media type with boundary', function () {
|
|
62
|
+
const res1 = parseContentType(
|
|
63
|
+
'multipart/form-data; boundary=---WebKitFormBoundary7MA4YWxkTrZu0gW',
|
|
64
|
+
);
|
|
65
|
+
expect(res1).to.be.eql({
|
|
66
|
+
mediaType: 'multipart/form-data',
|
|
67
|
+
charset: undefined,
|
|
68
|
+
boundary: '---WebKitFormBoundary7MA4YWxkTrZu0gW',
|
|
69
|
+
});
|
|
70
|
+
const res2 = parseContentType(
|
|
71
|
+
'multipart/form-data; boundary=---WebKitFormBoundary7MA4YWxkTrZu0gW;',
|
|
72
|
+
);
|
|
73
|
+
expect(res2).to.be.eql({
|
|
74
|
+
mediaType: 'multipart/form-data',
|
|
75
|
+
charset: undefined,
|
|
76
|
+
boundary: '---WebKitFormBoundary7MA4YWxkTrZu0gW',
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parsed cookies.
|
|
3
|
+
*/
|
|
4
|
+
type ParsedCookies = {
|
|
5
|
+
[key: string]: string | undefined;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Parse cookie string.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* parseCookieString('pkg=math; equation=E%3Dmc%5E2');
|
|
14
|
+
* // {pkg: 'math', equation: 'E=mc^2'}
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @param input
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseCookieString(input: string): ParsedCookies;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {InvalidArgumentError} from '@e22m4u/js-format';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse cookie string.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* parseCookieString('pkg=math; equation=E%3Dmc%5E2');
|
|
9
|
+
* // {pkg: 'math', equation: 'E=mc^2'}
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* @param {string} input
|
|
13
|
+
* @returns {object}
|
|
14
|
+
*/
|
|
15
|
+
export function parseCookieString(input) {
|
|
16
|
+
if (typeof input !== 'string') {
|
|
17
|
+
throw new InvalidArgumentError(
|
|
18
|
+
'Parameter "input" must be a String, but %v was given.',
|
|
19
|
+
input,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return input
|
|
23
|
+
.split(';')
|
|
24
|
+
.filter(v => v !== '')
|
|
25
|
+
.map(v => v.split('='))
|
|
26
|
+
.reduce((cookies, tuple) => {
|
|
27
|
+
const key = decodeURIComponent(tuple[0]).trim();
|
|
28
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
29
|
+
return cookies;
|
|
30
|
+
}
|
|
31
|
+
const value =
|
|
32
|
+
tuple[1] !== undefined ? decodeURIComponent(tuple[1]).trim() : '';
|
|
33
|
+
cookies[key] = value;
|
|
34
|
+
return cookies;
|
|
35
|
+
}, {});
|
|
36
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {expect} from 'chai';
|
|
2
|
+
import {format} from '@e22m4u/js-format';
|
|
3
|
+
import {parseCookieString} from './parse-cookie-string.js';
|
|
4
|
+
|
|
5
|
+
describe('parseCookieString', function () {
|
|
6
|
+
it('should require the parameter "input" to be a String', function () {
|
|
7
|
+
const throwable = v => () => parseCookieString(v);
|
|
8
|
+
const error = v =>
|
|
9
|
+
format('Parameter "input" must be a String, but %s was given.', v);
|
|
10
|
+
expect(throwable(10)).to.throw(error('10'));
|
|
11
|
+
expect(throwable(0)).to.throw(error('0'));
|
|
12
|
+
expect(throwable(true)).to.throw(error('true'));
|
|
13
|
+
expect(throwable(false)).to.throw(error('false'));
|
|
14
|
+
expect(throwable(null)).to.throw(error('null'));
|
|
15
|
+
expect(throwable({})).to.throw(error('Object'));
|
|
16
|
+
expect(throwable([])).to.throw(error('Array'));
|
|
17
|
+
expect(throwable(undefined)).to.throw(error('undefined'));
|
|
18
|
+
throwable('str')();
|
|
19
|
+
throwable('')();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return cookies as a plain object', function () {
|
|
23
|
+
const value = 'pkg=math; equation=E%3Dmc%5E2';
|
|
24
|
+
const result = parseCookieString(value);
|
|
25
|
+
expect(result).to.have.property('pkg', 'math');
|
|
26
|
+
expect(result).to.have.property('equation', 'E=mc^2');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return an empty object for an empty string', function () {
|
|
30
|
+
const result = parseCookieString('');
|
|
31
|
+
expect(result).to.be.eql({});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should parse an empty cookie as an empty string', function () {
|
|
35
|
+
const result = parseCookieString('foo=bar; baz');
|
|
36
|
+
expect(result).to.be.eql({foo: 'bar', baz: ''});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should ignore prototype properties', function () {
|
|
40
|
+
const result = parseCookieString(
|
|
41
|
+
'__proto__=a; constructor=b; prototype=c; foo=bar',
|
|
42
|
+
);
|
|
43
|
+
expect(result).to.be.eql({foo: 'bar'});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
File without changes
|
|
File without changes
|