Skip to content

Commit e0faee1

Browse files
committed
Problem Details API
- Change all error responses to Problem Details - UUIDv7 - PublicPathMiddleware: Directory auto index - CORSMiddleware: Access-Control-Allow-Credentials - Add support for Range when sending a file
1 parent 09699e1 commit e0faee1

34 files changed

+1072
-305
lines changed

README.md

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,28 @@ Midori is an opinionated Web API Framework designed for Node.js, using Node.js's
1919
- [x] JWE
2020
- [x] JWK
2121
- [x] CORS
22+
- [x] Content Security Policy
2223
- [x] Static Files
23-
- [x] Body Parser
24-
- [x] JSON BigInt support
24+
- [x] Response Compression using `node:zlib` module
25+
- [x] Gzip
26+
- [x] Deflate
27+
- [x] Brotli
28+
- [x] Extensible Body Parser and Response Serializer
29+
- [x] JSON+BigInt
30+
- [x] CSV
31+
- [x] Form
32+
- [x] Multipart
33+
- [x] Streams
2534
- [x] Hashing using native Node.js `node:crypto` module
2635
- [x] PBKDF2
2736
- [x] Scrypt
2837
- [x] SHA-256
2938
- [x] SHA-512
30-
- [x] Basic Validation
39+
- [x] Problem Details
40+
- [x] Request Validation
41+
- [x] Auth
42+
- [x] Basic
43+
- [x] Bearer
3144

3245
## Roadmap
3346
- [ ] Tests
@@ -149,7 +162,10 @@ The HTTP module is responsible for handling HTTP requests and responses.
149162
import { Application } from 'midori/app';
150163
import { Request, Response } from 'midori/http';
151164

152-
const handler = async (req: Request, app: Application): Promise<Response> {
165+
const handler = async (
166+
req: Request,
167+
app: Application
168+
): Promise<Response> {
153169
// ...
154170
}
155171
```
@@ -158,7 +174,11 @@ const handler = async (req: Request, app: Application): Promise<Response> {
158174
import { Application } from 'midori/app';
159175
import { Request, Response } from 'midori/http';
160176

161-
const middleware = async (req: Request, next: (req: Request) => Promise<Response>, app: Application): Promise<Response> {
177+
const middleware = async (
178+
req: Request,
179+
next: (req: Request) => Promise<Response>,
180+
app: Application
181+
): Promise<Response> {
162182
// ...
163183
}
164184
```

package-lock.json

Lines changed: 15 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modscleo4/midori",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "Midori is a Node.js web API framework with minimal dependencies and based on PSR ideas.",
55
"type": "module",
66
"keywords": [
@@ -101,8 +101,8 @@
101101
},
102102
"devDependencies": {
103103
"@types/mime-types": "^2.1.4",
104-
"@types/node": "^20.10.0",
105-
"typescript": "^5.3.2"
104+
"@types/node": "^20.12.7",
105+
"typescript": "^5.4.5"
106106
},
107107
"dependencies": {
108108
"mime-types": "^2.1.35"

src/errors/HTTPError.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { OutgoingHttpHeaders } from "http";
18+
1719
/**
1820
* Basic HTTP Error with a status code and message.
1921
* The HTTPErrorMiddleware will catch this error and send the status code and message to the client.
@@ -23,14 +25,17 @@ export default class HTTPError extends Error {
2325

2426
constructor(
2527
message: string,
26-
public status: number = 500
28+
public status: number = 500,
29+
public extra: Record<string, unknown> = {},
30+
public extraHeaders: OutgoingHttpHeaders = {},
2731
) {
2832
super(message);
2933
}
3034

3135
toJSON(): object {
3236
return {
3337
message: this.message,
38+
...this.extra,
3439
};
3540
}
3641
}

src/errors/ValidationError.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,6 @@ export default class ValidationError extends HTTPError {
2222
message: string = 'Some fields are invalid',
2323
status: number = 400,
2424
) {
25-
super(message, status);
26-
}
27-
28-
override toJSON(): object {
29-
return {
30-
...super.toJSON(),
31-
errors: this.errors,
32-
};
25+
super(message, status, { errors });
3326
}
3427
}

src/http/EStatusCode.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,140 @@ export enum EStatusCode {
8787
NOT_EXTENDED = 510,
8888
NETWORK_AUTHENTICATION_REQUIRED = 511
8989
}
90+
91+
export function titleFromStatus(status: number): string | null {
92+
switch (status) {
93+
case EStatusCode.CONTINUE:
94+
return 'Continue';
95+
case EStatusCode.SWITCHING_PROTOCOLS:
96+
return 'Switching Protocols';
97+
case EStatusCode.PROCESSING:
98+
return 'Processing';
99+
case EStatusCode.EARLY_HINTS:
100+
return 'Early Hints';
101+
102+
case EStatusCode.OK:
103+
return 'OK';
104+
case EStatusCode.CREATED:
105+
return 'Created';
106+
case EStatusCode.ACCEPTED:
107+
return 'Accepted';
108+
case EStatusCode.NON_AUTHORITATIVE_INFORMATION:
109+
return 'Non-Authoritative Information';
110+
case EStatusCode.NO_CONTENT:
111+
return 'No Content';
112+
case EStatusCode.RESET_CONTENT:
113+
return 'Reset Content';
114+
case EStatusCode.PARTIAL_CONTENT:
115+
return 'Partial Content';
116+
case EStatusCode.MULTI_STATUS:
117+
return 'Multi-Status';
118+
case EStatusCode.ALREADY_REPORTED:
119+
return 'Already Reported';
120+
case EStatusCode.IM_USED:
121+
return 'IM Used';
122+
123+
case EStatusCode.MULTIPLE_CHOICES:
124+
return 'Multiple Choices';
125+
case EStatusCode.MOVED_PERMANENTLY:
126+
return 'Moved Permanently';
127+
case EStatusCode.FOUND:
128+
return 'Found';
129+
case EStatusCode.SEE_OTHER:
130+
return 'See Other';
131+
case EStatusCode.NOT_MODIFIED:
132+
return 'Not Modified';
133+
case EStatusCode.USE_PROXY:
134+
return 'Use Proxy';
135+
case EStatusCode.SWITCH_PROXY:
136+
return 'Switch Proxy';
137+
case EStatusCode.TEMPORARY_REDIRECT:
138+
return 'Temporary Redirect';
139+
case EStatusCode.PERMANENT_REDIRECT:
140+
return 'Permanent Redirect';
141+
142+
case EStatusCode.BAD_REQUEST:
143+
return 'Bad Request';
144+
case EStatusCode.UNAUTHORIZED:
145+
return 'Unauthorized';
146+
case EStatusCode.PAYMENT_REQUIRED:
147+
return 'Payment Required';
148+
case EStatusCode.FORBIDDEN:
149+
return 'Forbidden';
150+
case EStatusCode.NOT_FOUND:
151+
return 'Not Found';
152+
case EStatusCode.METHOD_NOT_ALLOWED:
153+
return 'Method Not Allowed';
154+
case EStatusCode.NOT_ACCEPTABLE:
155+
return 'Not Acceptable';
156+
case EStatusCode.PROXY_AUTHENTICATION_REQUIRED:
157+
return 'Proxy Authentication Required';
158+
case EStatusCode.REQUEST_TIMEOUT:
159+
return 'Request Timeout';
160+
case EStatusCode.CONFLICT:
161+
return 'Conflict';
162+
case EStatusCode.GONE:
163+
return 'Gone';
164+
case EStatusCode.LENGTH_REQUIRED:
165+
return 'Length Required';
166+
case EStatusCode.PRECONDITION_FAILED:
167+
return 'Precondition Failed';
168+
case EStatusCode.PAYLOAD_TOO_LARGE:
169+
return 'Payload Too Large';
170+
case EStatusCode.URI_TOO_LONG:
171+
return 'URI Too Long';
172+
case EStatusCode.UNSUPPORTED_MEDIA_TYPE:
173+
return 'Unsupported Media Type';
174+
case EStatusCode.RANGE_NOT_SATISFIABLE:
175+
return 'Range Not Satisfiable';
176+
case EStatusCode.EXPECTATION_FAILED:
177+
return 'Expectation Failed';
178+
case EStatusCode.I_AM_A_TEAPOT:
179+
return 'I\'m a teapot';
180+
case EStatusCode.MISDIRECTED_REQUEST:
181+
return 'Misdirected Request';
182+
case EStatusCode.UNPROCESSABLE_ENTITY:
183+
return 'Unprocessable Entity';
184+
case EStatusCode.LOCKED:
185+
return 'Locked';
186+
case EStatusCode.FAILED_DEPENDENCY:
187+
return 'Failed Dependency';
188+
case EStatusCode.TOO_EARLY:
189+
return 'Too Early';
190+
case EStatusCode.UPGRADE_REQUIRED:
191+
return 'Upgrade Required';
192+
case EStatusCode.PRECONDITION_REQUIRED:
193+
return 'Precondition Required';
194+
case EStatusCode.TOO_MANY_REQUESTS:
195+
return 'Too Many Requests';
196+
case EStatusCode.REQUEST_HEADER_FIELDS_TOO_LARGE:
197+
return 'Request Header Fields Too Large';
198+
case EStatusCode.UNAVAILABLE_FOR_LEGAL_REASONS:
199+
return 'Unavailable For Legal Reasons';
200+
201+
case EStatusCode.INTERNAL_SERVER_ERROR:
202+
return 'Internal Server Error';
203+
case EStatusCode.NOT_IMPLEMENTED:
204+
return 'Not Implemented';
205+
case EStatusCode.BAD_GATEWAY:
206+
return 'Bad Gateway';
207+
case EStatusCode.SERVICE_UNAVAILABLE:
208+
return 'Service Unavailable';
209+
case EStatusCode.GATEWAY_TIMEOUT:
210+
return 'Gateway Timeout';
211+
case EStatusCode.HTTP_VERSION_NOT_SUPPORTED:
212+
return 'HTTP Version Not Supported';
213+
case EStatusCode.VARIANT_ALSO_NEGOTIATES:
214+
return 'Variant Also Negotiates';
215+
case EStatusCode.INSUFFICIENT_STORAGE:
216+
return 'Insufficient Storage';
217+
case EStatusCode.LOOP_DETECTED:
218+
return 'Loop Detected';
219+
case EStatusCode.NOT_EXTENDED:
220+
return 'Not Extended';
221+
case EStatusCode.NETWORK_AUTHENTICATION_REQUIRED:
222+
return 'Network Authentication Required';
223+
}
224+
225+
return null;
226+
}

src/http/Request.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default class Request<T = unknown> extends IncomingMessage {
3838
#path?: string;
3939
#body: Buffer[] = [];
4040
#parsedBody?: T = undefined;
41-
#container?: Container<string | symbol, unknown> = new Container();
41+
#container: Container<string | symbol, unknown> = new Container();
4242
#ip: string | undefined;
4343
#acceptPriority: string[] = [];
4444

@@ -49,7 +49,7 @@ export default class Request<T = unknown> extends IncomingMessage {
4949
const url = new URL(this.url ?? '', `${this.headers['x-forwarded-proto'] ?? 'http'}://${this.headers.host}`);
5050

5151
this.#query = url.searchParams;
52-
this.#path = url.pathname;
52+
this.#path = decodeURIComponent(url.pathname);
5353
this.#ip = this.socket.remoteAddress;
5454
if (this.headers['x-real-ip']) {
5555
this.#ip = Array.isArray(this.headers['x-real-ip']) ? this.headers['x-real-ip'][0] : this.headers['x-real-ip'];
@@ -142,7 +142,7 @@ export default class Request<T = unknown> extends IncomingMessage {
142142
}
143143

144144
get container() {
145-
return this.#container!;
145+
return this.#container;
146146
}
147147

148148
get ip() {

0 commit comments

Comments
 (0)