@navios/core 0.9.2 → 1.0.0-alpha.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/CHANGELOG.md +41 -0
- package/README.md +1 -1
- package/docs/README.md +3 -1
- package/docs/exceptions.md +37 -1
- package/lib/{index-B2ulzZIr.d.cts → index-BOBeqs9U.d.cts} +445 -2
- package/lib/index-BOBeqs9U.d.cts.map +1 -0
- package/lib/{index-C8lUQePd.d.mts → index-CVlRKxp2.d.mts} +445 -2
- package/lib/index-CVlRKxp2.d.mts.map +1 -0
- package/lib/index.cjs +37 -2
- package/lib/index.d.cts +2 -2
- package/lib/index.d.mts +2 -2
- package/lib/index.mjs +2 -2
- package/lib/legacy-compat/index.cjs +2 -2
- package/lib/legacy-compat/index.cjs.map +1 -1
- package/lib/legacy-compat/index.d.cts +7 -5
- package/lib/legacy-compat/index.d.cts.map +1 -1
- package/lib/legacy-compat/index.d.mts +7 -5
- package/lib/legacy-compat/index.d.mts.map +1 -1
- package/lib/legacy-compat/index.mjs.map +1 -1
- package/lib/{src-Baabu2R8.mjs → src-DqPmV_Qw.mjs} +1743 -26
- package/lib/src-DqPmV_Qw.mjs.map +1 -0
- package/lib/{src-Cu9OAnfp.cjs → src-dwaFq8LO.cjs} +1948 -171
- package/lib/src-dwaFq8LO.cjs.map +1 -0
- package/lib/testing/index.cjs +2 -2
- package/lib/testing/index.d.cts +1 -1
- package/lib/testing/index.d.mts +1 -1
- package/lib/testing/index.mjs +1 -1
- package/lib/{use-guards.decorator-EvI2m2DK.cjs → use-guards.decorator-C_cBWxyM.cjs} +2 -2
- package/lib/{use-guards.decorator-EvI2m2DK.cjs.map → use-guards.decorator-C_cBWxyM.cjs.map} +1 -1
- package/package.json +4 -4
- package/src/__tests__/responders.spec.mts +339 -0
- package/src/index.mts +1 -0
- package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +0 -1
- package/src/legacy-compat/attribute.factory.mts +12 -26
- package/src/responders/enums/framework-error.enum.mts +38 -0
- package/src/responders/enums/index.mts +1 -0
- package/src/responders/index.mts +4 -0
- package/src/responders/interfaces/error-responder.interface.mts +42 -0
- package/src/responders/interfaces/error-response.interface.mts +22 -0
- package/src/responders/interfaces/index.mts +3 -0
- package/src/responders/interfaces/problem-details.interface.mts +34 -0
- package/src/responders/services/error-response-producer.service.mts +143 -0
- package/src/responders/services/forbidden-responder.service.mts +77 -0
- package/src/responders/services/index.mts +5 -0
- package/src/responders/services/internal-server-error-responder.service.mts +57 -0
- package/src/responders/services/not-found-responder.service.mts +76 -0
- package/src/responders/services/validation-error-responder.service.mts +78 -0
- package/src/responders/tokens/index.mts +1 -0
- package/src/responders/tokens/responder.tokens.mts +84 -0
- package/src/services/guard-runner.service.mts +19 -6
- package/lib/index-B2ulzZIr.d.cts.map +0 -1
- package/lib/index-C8lUQePd.d.mts.map +0 -1
- package/lib/src-Baabu2R8.mjs.map +0 -1
- package/lib/src-Cu9OAnfp.cjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ClassType } from '@navios/di'
|
|
2
1
|
import type { z, ZodType } from 'zod/v4'
|
|
2
|
+
import type { ClassType } from '@navios/di'
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
ControllerMetadata,
|
|
@@ -21,40 +21,26 @@ import {
|
|
|
21
21
|
import { createClassContext, createMethodContext } from './context-compat.mjs'
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Type for a legacy class attribute decorator without a value.
|
|
24
|
+
* Type for a legacy class/method attribute decorator without a value.
|
|
25
25
|
*
|
|
26
26
|
* Attributes are custom metadata decorators that can be applied to modules,
|
|
27
27
|
* controllers, and endpoints.
|
|
28
28
|
*/
|
|
29
|
-
export type LegacyClassAttribute =
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
target: T,
|
|
34
|
-
propertyKey: string | symbol,
|
|
35
|
-
descriptor: PropertyDescriptor,
|
|
36
|
-
) => PropertyDescriptor) & {
|
|
37
|
-
token: symbol
|
|
38
|
-
}
|
|
29
|
+
export type LegacyClassAttribute = {
|
|
30
|
+
(): ClassDecorator & MethodDecorator
|
|
31
|
+
token: symbol
|
|
32
|
+
}
|
|
39
33
|
|
|
40
34
|
/**
|
|
41
|
-
* Type for a legacy class attribute decorator with a validated value.
|
|
35
|
+
* Type for a legacy class/method attribute decorator with a validated value.
|
|
42
36
|
*
|
|
43
37
|
* @typeParam S - The Zod schema type for validation
|
|
44
38
|
*/
|
|
45
|
-
export type LegacyClassSchemaAttribute<S extends ZodType> =
|
|
46
|
-
value: z.input<S
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
) => <T extends object>(
|
|
51
|
-
target: T,
|
|
52
|
-
propertyKey: string | symbol,
|
|
53
|
-
descriptor: PropertyDescriptor,
|
|
54
|
-
) => PropertyDescriptor) & {
|
|
55
|
-
token: symbol
|
|
56
|
-
schema: ZodType
|
|
57
|
-
}
|
|
39
|
+
export type LegacyClassSchemaAttribute<S extends ZodType> = {
|
|
40
|
+
(value: z.input<S>): ClassDecorator & MethodDecorator
|
|
41
|
+
token: symbol
|
|
42
|
+
schema: ZodType
|
|
43
|
+
}
|
|
58
44
|
|
|
59
45
|
/**
|
|
60
46
|
* Legacy-compatible factory for creating custom attribute decorators.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enumeration of framework-level error types.
|
|
3
|
+
* Used to explicitly specify which error responder should handle an error.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* // In adapter error handling
|
|
8
|
+
* if (error instanceof ZodError) {
|
|
9
|
+
* return errorProducer.respond(FrameworkError.ValidationError, error)
|
|
10
|
+
* }
|
|
11
|
+
* return errorProducer.handleUnknown(error)
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export enum FrameworkError {
|
|
15
|
+
/**
|
|
16
|
+
* Resource not found (HTTP 404).
|
|
17
|
+
* Use when a requested resource does not exist.
|
|
18
|
+
*/
|
|
19
|
+
NotFound = 'NotFound',
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Forbidden (HTTP 403).
|
|
23
|
+
* Use when access to a resource is denied (e.g., guard rejection).
|
|
24
|
+
*/
|
|
25
|
+
Forbidden = 'Forbidden',
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Internal server error (HTTP 500).
|
|
29
|
+
* Use for unexpected errors that don't fit other categories.
|
|
30
|
+
*/
|
|
31
|
+
InternalServerError = 'InternalServerError',
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validation error (HTTP 400).
|
|
35
|
+
* Use when request data fails validation (e.g., Zod errors).
|
|
36
|
+
*/
|
|
37
|
+
ValidationError = 'ValidationError',
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './framework-error.enum.mjs'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ErrorResponse } from './error-response.interface.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface for error responders that convert errors to HTTP responses.
|
|
5
|
+
* Each responder handles a specific type of framework error and produces
|
|
6
|
+
* an RFC 7807 compliant response.
|
|
7
|
+
*
|
|
8
|
+
* Implementations are registered with low priority (-10) so they can be
|
|
9
|
+
* easily overridden by consumers.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* @Injectable({
|
|
14
|
+
* token: InternalServerErrorResponderToken,
|
|
15
|
+
* priority: 0, // Override default implementation
|
|
16
|
+
* })
|
|
17
|
+
* export class CustomInternalErrorResponder implements ErrorResponder {
|
|
18
|
+
* getResponse(error: unknown, description?: string): ErrorResponse {
|
|
19
|
+
* return {
|
|
20
|
+
* statusCode: 500,
|
|
21
|
+
* payload: {
|
|
22
|
+
* type: 'https://api.myapp.com/errors/server-error',
|
|
23
|
+
* title: 'Server Error',
|
|
24
|
+
* status: 500,
|
|
25
|
+
* detail: description ?? 'An unexpected error occurred',
|
|
26
|
+
* },
|
|
27
|
+
* headers: { 'Content-Type': 'application/problem+json' },
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export interface ErrorResponder {
|
|
34
|
+
/**
|
|
35
|
+
* Converts an error to an ErrorResponse with RFC 7807 Problem Details.
|
|
36
|
+
*
|
|
37
|
+
* @param error - The original error that was thrown
|
|
38
|
+
* @param description - Optional custom description to include in the response
|
|
39
|
+
* @returns ErrorResponse with status code, payload, and headers
|
|
40
|
+
*/
|
|
41
|
+
getResponse(error: unknown, description?: string): ErrorResponse
|
|
42
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ProblemDetails } from './problem-details.interface.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a complete error response that can be sent to the client.
|
|
5
|
+
* Includes the status code, payload (RFC 7807 Problem Details), and headers.
|
|
6
|
+
*/
|
|
7
|
+
export interface ErrorResponse {
|
|
8
|
+
/**
|
|
9
|
+
* HTTP status code for the response.
|
|
10
|
+
*/
|
|
11
|
+
statusCode: number
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* RFC 7807 Problem Details payload.
|
|
15
|
+
*/
|
|
16
|
+
payload: ProblemDetails
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* HTTP headers to include in the response.
|
|
20
|
+
*/
|
|
21
|
+
headers: Record<string, string>
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 7807 Problem Details for HTTP APIs
|
|
3
|
+
* @see https://tools.ietf.org/html/rfc7807
|
|
4
|
+
*/
|
|
5
|
+
export interface ProblemDetails {
|
|
6
|
+
/**
|
|
7
|
+
* A URI reference that identifies the problem type.
|
|
8
|
+
* When dereferenced, it should provide human-readable documentation.
|
|
9
|
+
* @default 'about:blank'
|
|
10
|
+
*/
|
|
11
|
+
type?: string
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A short, human-readable summary of the problem type.
|
|
15
|
+
* It should not change from occurrence to occurrence.
|
|
16
|
+
*/
|
|
17
|
+
title: string
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The HTTP status code for this occurrence of the problem.
|
|
21
|
+
*/
|
|
22
|
+
status: number
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A human-readable explanation specific to this occurrence of the problem.
|
|
26
|
+
*/
|
|
27
|
+
detail?: string
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Additional extension members.
|
|
31
|
+
* Custom properties can be added to provide more context.
|
|
32
|
+
*/
|
|
33
|
+
[key: string]: unknown
|
|
34
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { inject, Injectable } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import { FrameworkError } from '../enums/framework-error.enum.mjs'
|
|
4
|
+
import type { ErrorResponse } from '../interfaces/error-response.interface.mjs'
|
|
5
|
+
import {
|
|
6
|
+
ForbiddenResponderToken,
|
|
7
|
+
InternalServerErrorResponderToken,
|
|
8
|
+
NotFoundResponderToken,
|
|
9
|
+
ValidationErrorResponderToken,
|
|
10
|
+
} from '../tokens/responder.tokens.mjs'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Service for producing standardized error responses.
|
|
14
|
+
*
|
|
15
|
+
* This service coordinates error responders to produce RFC 7807 compliant
|
|
16
|
+
* Problem Details responses. Adapters use this service to convert errors
|
|
17
|
+
* into standardized HTTP responses.
|
|
18
|
+
*
|
|
19
|
+
* The caller explicitly specifies which type of error response to produce
|
|
20
|
+
* via the FrameworkError enum, giving full control to the adapter.
|
|
21
|
+
*
|
|
22
|
+
* @example Usage in an adapter:
|
|
23
|
+
* ```typescript
|
|
24
|
+
* @Injectable()
|
|
25
|
+
* class MyAdapter {
|
|
26
|
+
* private errorProducer = inject(ErrorResponseProducerService)
|
|
27
|
+
*
|
|
28
|
+
* handleError(error: unknown): Response {
|
|
29
|
+
* if (error instanceof ZodError) {
|
|
30
|
+
* const response = this.errorProducer.respond(
|
|
31
|
+
* FrameworkError.ValidationError,
|
|
32
|
+
* error,
|
|
33
|
+
* )
|
|
34
|
+
* return new Response(JSON.stringify(response.payload), {
|
|
35
|
+
* status: response.statusCode,
|
|
36
|
+
* headers: response.headers,
|
|
37
|
+
* })
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* // Fallback for unknown errors
|
|
41
|
+
* const response = this.errorProducer.handleUnknown(error)
|
|
42
|
+
* return new Response(JSON.stringify(response.payload), {
|
|
43
|
+
* status: response.statusCode,
|
|
44
|
+
* headers: response.headers,
|
|
45
|
+
* })
|
|
46
|
+
* }
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
@Injectable()
|
|
51
|
+
export class ErrorResponseProducerService {
|
|
52
|
+
private readonly forbiddenResponder = inject(ForbiddenResponderToken)
|
|
53
|
+
private readonly internalServerErrorResponder = inject(
|
|
54
|
+
InternalServerErrorResponderToken,
|
|
55
|
+
)
|
|
56
|
+
private readonly notFoundResponder = inject(NotFoundResponderToken)
|
|
57
|
+
private readonly validationErrorResponder = inject(
|
|
58
|
+
ValidationErrorResponderToken,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Produces an error response for a specific framework error type.
|
|
63
|
+
*
|
|
64
|
+
* @param type - The type of framework error (from FrameworkError enum)
|
|
65
|
+
* @param error - The original error that was thrown
|
|
66
|
+
* @param description - Optional custom description to include in the response
|
|
67
|
+
* @returns ErrorResponse with status code, RFC 7807 payload, and headers
|
|
68
|
+
*/
|
|
69
|
+
respond(
|
|
70
|
+
type: FrameworkError,
|
|
71
|
+
error: unknown,
|
|
72
|
+
description?: string,
|
|
73
|
+
): ErrorResponse {
|
|
74
|
+
switch (type) {
|
|
75
|
+
case FrameworkError.NotFound:
|
|
76
|
+
return this.notFoundResponder.getResponse(error, description)
|
|
77
|
+
case FrameworkError.Forbidden:
|
|
78
|
+
return this.forbiddenResponder.getResponse(error, description)
|
|
79
|
+
case FrameworkError.InternalServerError:
|
|
80
|
+
return this.internalServerErrorResponder.getResponse(error, description)
|
|
81
|
+
case FrameworkError.ValidationError:
|
|
82
|
+
return this.validationErrorResponder.getResponse(error, description)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handles unknown errors by producing an Internal Server Error response.
|
|
88
|
+
*
|
|
89
|
+
* Use this as a fallback when the error type is not known or doesn't
|
|
90
|
+
* match any specific framework error type.
|
|
91
|
+
*
|
|
92
|
+
* @param error - The original error that was thrown
|
|
93
|
+
* @param description - Optional custom description to include in the response
|
|
94
|
+
* @returns ErrorResponse with 500 status code
|
|
95
|
+
*/
|
|
96
|
+
handleUnknown(error: unknown, description?: string): ErrorResponse {
|
|
97
|
+
return this.internalServerErrorResponder.getResponse(error, description)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Convenience method to produce a Not Found error response.
|
|
102
|
+
*
|
|
103
|
+
* @param error - The original error that was thrown
|
|
104
|
+
* @param description - Optional custom description
|
|
105
|
+
* @returns ErrorResponse with 404 status code
|
|
106
|
+
*/
|
|
107
|
+
notFound(error: unknown, description?: string): ErrorResponse {
|
|
108
|
+
return this.notFoundResponder.getResponse(error, description)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convenience method to produce a Validation Error response.
|
|
113
|
+
*
|
|
114
|
+
* @param error - The original error (typically a ZodError)
|
|
115
|
+
* @param description - Optional custom description
|
|
116
|
+
* @returns ErrorResponse with 400 status code
|
|
117
|
+
*/
|
|
118
|
+
validationError(error: unknown, description?: string): ErrorResponse {
|
|
119
|
+
return this.validationErrorResponder.getResponse(error, description)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Convenience method to produce an Internal Server Error response.
|
|
124
|
+
*
|
|
125
|
+
* @param error - The original error
|
|
126
|
+
* @param description - Optional custom description
|
|
127
|
+
* @returns ErrorResponse with 500 status code
|
|
128
|
+
*/
|
|
129
|
+
internalServerError(error: unknown, description?: string): ErrorResponse {
|
|
130
|
+
return this.internalServerErrorResponder.getResponse(error, description)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Convenience method to produce a Forbidden error response.
|
|
135
|
+
*
|
|
136
|
+
* @param error - The original error
|
|
137
|
+
* @param description - Optional custom description
|
|
138
|
+
* @returns ErrorResponse with 403 status code
|
|
139
|
+
*/
|
|
140
|
+
forbidden(error: unknown, description?: string): ErrorResponse {
|
|
141
|
+
return this.forbiddenResponder.getResponse(error, description)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Injectable } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import type { ErrorResponder } from '../interfaces/error-responder.interface.mjs'
|
|
4
|
+
import type { ErrorResponse } from '../interfaces/error-response.interface.mjs'
|
|
5
|
+
import { ForbiddenResponderToken } from '../tokens/responder.tokens.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default responder for forbidden errors (HTTP 403).
|
|
9
|
+
*
|
|
10
|
+
* Converts errors to RFC 7807 Problem Details format.
|
|
11
|
+
* Used when access to a resource is denied (e.g., guard rejection).
|
|
12
|
+
* Registered with low priority (-10) so it can be easily overridden.
|
|
13
|
+
*
|
|
14
|
+
* @example Override with custom implementation:
|
|
15
|
+
* ```typescript
|
|
16
|
+
* @Injectable({
|
|
17
|
+
* token: ForbiddenResponderToken,
|
|
18
|
+
* priority: 0,
|
|
19
|
+
* })
|
|
20
|
+
* export class CustomForbiddenResponder implements ErrorResponder {
|
|
21
|
+
* getResponse(error: unknown, description?: string): ErrorResponse {
|
|
22
|
+
* return {
|
|
23
|
+
* statusCode: 403,
|
|
24
|
+
* payload: {
|
|
25
|
+
* type: 'https://api.myapp.com/errors/forbidden',
|
|
26
|
+
* title: 'Access Denied',
|
|
27
|
+
* status: 403,
|
|
28
|
+
* detail: description ?? 'You do not have permission to access this resource',
|
|
29
|
+
* },
|
|
30
|
+
* headers: { 'Content-Type': 'application/problem+json' },
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
@Injectable({
|
|
37
|
+
token: ForbiddenResponderToken,
|
|
38
|
+
priority: -10,
|
|
39
|
+
})
|
|
40
|
+
export class ForbiddenResponderService implements ErrorResponder {
|
|
41
|
+
getResponse(error: unknown, description?: string): ErrorResponse {
|
|
42
|
+
// Explicit description takes priority
|
|
43
|
+
if (description) {
|
|
44
|
+
return this.createResponse(description)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Try to extract detail from error with a response property (like ForbiddenException)
|
|
48
|
+
if (
|
|
49
|
+
error &&
|
|
50
|
+
typeof error === 'object' &&
|
|
51
|
+
'response' in error &&
|
|
52
|
+
error.response
|
|
53
|
+
) {
|
|
54
|
+
if (typeof error.response === 'string') {
|
|
55
|
+
return this.createResponse(error.response)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Default message
|
|
60
|
+
return this.createResponse('Access to this resource is forbidden')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private createResponse(detail: string): ErrorResponse {
|
|
64
|
+
return {
|
|
65
|
+
statusCode: 403,
|
|
66
|
+
payload: {
|
|
67
|
+
type: 'about:blank',
|
|
68
|
+
title: 'Forbidden',
|
|
69
|
+
status: 403,
|
|
70
|
+
detail,
|
|
71
|
+
},
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/problem+json',
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from './error-response-producer.service.mjs'
|
|
2
|
+
export * from './forbidden-responder.service.mjs'
|
|
3
|
+
export * from './internal-server-error-responder.service.mjs'
|
|
4
|
+
export * from './not-found-responder.service.mjs'
|
|
5
|
+
export * from './validation-error-responder.service.mjs'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Injectable } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import type { ErrorResponder } from '../interfaces/error-responder.interface.mjs'
|
|
4
|
+
import type { ErrorResponse } from '../interfaces/error-response.interface.mjs'
|
|
5
|
+
import { InternalServerErrorResponderToken } from '../tokens/responder.tokens.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default responder for internal server errors (HTTP 500).
|
|
9
|
+
*
|
|
10
|
+
* Converts generic errors to RFC 7807 Problem Details format.
|
|
11
|
+
* Registered with low priority (-10) so it can be easily overridden.
|
|
12
|
+
*
|
|
13
|
+
* @example Override with custom implementation:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Injectable({
|
|
16
|
+
* token: InternalServerErrorResponderToken,
|
|
17
|
+
* priority: 0,
|
|
18
|
+
* })
|
|
19
|
+
* export class CustomInternalErrorResponder implements ErrorResponder {
|
|
20
|
+
* getResponse(error: unknown, description?: string): ErrorResponse {
|
|
21
|
+
* return {
|
|
22
|
+
* statusCode: 500,
|
|
23
|
+
* payload: {
|
|
24
|
+
* type: 'https://api.myapp.com/errors/server-error',
|
|
25
|
+
* title: 'Server Error',
|
|
26
|
+
* status: 500,
|
|
27
|
+
* detail: description ?? 'An unexpected error occurred',
|
|
28
|
+
* },
|
|
29
|
+
* headers: { 'Content-Type': 'application/problem+json' },
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
@Injectable({
|
|
36
|
+
token: InternalServerErrorResponderToken,
|
|
37
|
+
priority: -10,
|
|
38
|
+
})
|
|
39
|
+
export class InternalServerErrorResponderService implements ErrorResponder {
|
|
40
|
+
getResponse(error: unknown, description?: string): ErrorResponse {
|
|
41
|
+
const message =
|
|
42
|
+
error instanceof Error ? error.message : 'Internal Server Error'
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
statusCode: 500,
|
|
46
|
+
payload: {
|
|
47
|
+
type: 'about:blank',
|
|
48
|
+
title: 'Internal Server Error',
|
|
49
|
+
status: 500,
|
|
50
|
+
detail: description ?? message,
|
|
51
|
+
},
|
|
52
|
+
headers: {
|
|
53
|
+
'Content-Type': 'application/problem+json',
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Injectable } from '@navios/di'
|
|
2
|
+
|
|
3
|
+
import type { ErrorResponder } from '../interfaces/error-responder.interface.mjs'
|
|
4
|
+
import type { ErrorResponse } from '../interfaces/error-response.interface.mjs'
|
|
5
|
+
import { NotFoundResponderToken } from '../tokens/responder.tokens.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default responder for not found errors (HTTP 404).
|
|
9
|
+
*
|
|
10
|
+
* Converts errors to RFC 7807 Problem Details format.
|
|
11
|
+
* Registered with low priority (-10) so it can be easily overridden.
|
|
12
|
+
*
|
|
13
|
+
* @example Override with custom implementation:
|
|
14
|
+
* ```typescript
|
|
15
|
+
* @Injectable({
|
|
16
|
+
* token: NotFoundResponderToken,
|
|
17
|
+
* priority: 0,
|
|
18
|
+
* })
|
|
19
|
+
* export class CustomNotFoundResponder implements ErrorResponder {
|
|
20
|
+
* getResponse(error: unknown, description?: string): ErrorResponse {
|
|
21
|
+
* return {
|
|
22
|
+
* statusCode: 404,
|
|
23
|
+
* payload: {
|
|
24
|
+
* type: 'https://api.myapp.com/errors/not-found',
|
|
25
|
+
* title: 'Resource Not Found',
|
|
26
|
+
* status: 404,
|
|
27
|
+
* detail: description ?? 'The requested resource was not found',
|
|
28
|
+
* },
|
|
29
|
+
* headers: { 'Content-Type': 'application/problem+json' },
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
@Injectable({
|
|
36
|
+
token: NotFoundResponderToken,
|
|
37
|
+
priority: -10,
|
|
38
|
+
})
|
|
39
|
+
export class NotFoundResponderService implements ErrorResponder {
|
|
40
|
+
getResponse(error: unknown, description?: string): ErrorResponse {
|
|
41
|
+
// Explicit description takes priority
|
|
42
|
+
if (description) {
|
|
43
|
+
return this.createResponse(description)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Try to extract detail from error with a response property (like NotFoundException)
|
|
47
|
+
if (
|
|
48
|
+
error &&
|
|
49
|
+
typeof error === 'object' &&
|
|
50
|
+
'response' in error &&
|
|
51
|
+
error.response
|
|
52
|
+
) {
|
|
53
|
+
if (typeof error.response === 'string') {
|
|
54
|
+
return this.createResponse(error.response)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Default message
|
|
59
|
+
return this.createResponse('The requested resource was not found')
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private createResponse(detail: string): ErrorResponse {
|
|
63
|
+
return {
|
|
64
|
+
statusCode: 404,
|
|
65
|
+
payload: {
|
|
66
|
+
type: 'about:blank',
|
|
67
|
+
title: 'Not Found',
|
|
68
|
+
status: 404,
|
|
69
|
+
detail,
|
|
70
|
+
},
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'application/problem+json',
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Injectable } from '@navios/di'
|
|
2
|
+
import { treeifyError, ZodError } from 'zod/v4'
|
|
3
|
+
|
|
4
|
+
import type { ErrorResponder } from '../interfaces/error-responder.interface.mjs'
|
|
5
|
+
import type { ErrorResponse } from '../interfaces/error-response.interface.mjs'
|
|
6
|
+
import { ValidationErrorResponderToken } from '../tokens/responder.tokens.mjs'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default responder for validation errors (HTTP 400).
|
|
10
|
+
*
|
|
11
|
+
* Converts Zod validation errors to RFC 7807 Problem Details format.
|
|
12
|
+
* Includes the structured validation errors from treeifyError.
|
|
13
|
+
* Registered with low priority (-10) so it can be easily overridden.
|
|
14
|
+
*
|
|
15
|
+
* @example Override with custom implementation:
|
|
16
|
+
* ```typescript
|
|
17
|
+
* @Injectable({
|
|
18
|
+
* token: ValidationErrorResponderToken,
|
|
19
|
+
* priority: 0,
|
|
20
|
+
* })
|
|
21
|
+
* export class CustomValidationResponder implements ErrorResponder {
|
|
22
|
+
* getResponse(error: unknown, description?: string): ErrorResponse {
|
|
23
|
+
* const zodError = error as ZodError
|
|
24
|
+
* return {
|
|
25
|
+
* statusCode: 422, // Use 422 instead of 400
|
|
26
|
+
* payload: {
|
|
27
|
+
* type: 'https://api.myapp.com/errors/validation',
|
|
28
|
+
* title: 'Unprocessable Entity',
|
|
29
|
+
* status: 422,
|
|
30
|
+
* detail: description ?? 'Validation failed',
|
|
31
|
+
* issues: zodError.issues,
|
|
32
|
+
* },
|
|
33
|
+
* headers: { 'Content-Type': 'application/problem+json' },
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
@Injectable({
|
|
40
|
+
token: ValidationErrorResponderToken,
|
|
41
|
+
priority: -10,
|
|
42
|
+
})
|
|
43
|
+
export class ValidationErrorResponderService implements ErrorResponder {
|
|
44
|
+
getResponse(error: unknown, description?: string): ErrorResponse {
|
|
45
|
+
// Handle ZodError specifically
|
|
46
|
+
if (error instanceof ZodError) {
|
|
47
|
+
return {
|
|
48
|
+
statusCode: 400,
|
|
49
|
+
payload: {
|
|
50
|
+
type: 'about:blank',
|
|
51
|
+
title: 'Validation Error',
|
|
52
|
+
status: 400,
|
|
53
|
+
detail: description ?? 'Request validation failed',
|
|
54
|
+
errors: treeifyError(error),
|
|
55
|
+
},
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/problem+json',
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fallback for non-Zod validation errors
|
|
63
|
+
return {
|
|
64
|
+
statusCode: 400,
|
|
65
|
+
payload: {
|
|
66
|
+
type: 'about:blank',
|
|
67
|
+
title: 'Validation Error',
|
|
68
|
+
status: 400,
|
|
69
|
+
detail:
|
|
70
|
+
description ??
|
|
71
|
+
(error instanceof Error ? error.message : 'Validation failed'),
|
|
72
|
+
},
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/problem+json',
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './responder.tokens.mjs'
|