@navios/core 0.9.3 → 1.0.0-alpha.2
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 +58 -0
- package/README.md +4 -2
- package/docs/README.md +3 -1
- package/docs/exceptions.md +37 -1
- package/lib/{index-B2ulzZIr.d.cts → index-BJjk2X1S.d.mts} +478 -33
- package/lib/index-BJjk2X1S.d.mts.map +1 -0
- package/lib/{index-C8lUQePd.d.mts → index-DZ6NU03y.d.cts} +478 -33
- package/lib/index-DZ6NU03y.d.cts.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 +3 -3
- package/lib/legacy-compat/index.cjs +8 -6
- package/lib/legacy-compat/index.cjs.map +1 -1
- package/lib/legacy-compat/index.d.cts +11 -16
- package/lib/legacy-compat/index.d.cts.map +1 -1
- package/lib/legacy-compat/index.d.mts +11 -16
- package/lib/legacy-compat/index.d.mts.map +1 -1
- package/lib/legacy-compat/index.mjs +7 -5
- package/lib/legacy-compat/index.mjs.map +1 -1
- package/lib/{src-Cu9OAnfp.cjs → src-C46ePe3d.cjs} +1970 -171
- package/lib/src-C46ePe3d.cjs.map +1 -0
- package/lib/{src-Baabu2R8.mjs → src-K2k0riYJ.mjs} +1941 -202
- package/lib/src-K2k0riYJ.mjs.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 +2 -2
- package/lib/{use-guards.decorator-EvI2m2DK.cjs → use-guards.decorator-B6tghdxM.cjs} +7 -4
- package/lib/use-guards.decorator-B6tghdxM.cjs.map +1 -0
- package/lib/{use-guards.decorator-ChJVzYLW.mjs → use-guards.decorator-Be_QUx6b.mjs} +6 -3
- package/lib/use-guards.decorator-Be_QUx6b.mjs.map +1 -0
- package/package.json +3 -3
- package/src/__tests__/responders.spec.mts +339 -0
- package/src/decorators/endpoint.decorator.mts +49 -97
- package/src/decorators/module.decorator.mts +12 -0
- package/src/decorators/multipart.decorator.mts +62 -87
- package/src/decorators/stream.decorator.mts +66 -76
- package/src/index.mts +1 -0
- package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +0 -2
- package/src/legacy-compat/decorators/endpoint.decorator.mts +21 -47
- package/src/legacy-compat/decorators/multipart.decorator.mts +20 -41
- package/src/legacy-compat/decorators/stream.decorator.mts +48 -42
- package/src/metadata/module.metadata.mts +2 -0
- 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/src/services/module-loader.service.mts +63 -0
- 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
- package/lib/use-guards.decorator-ChJVzYLW.mjs.map +0 -1
- package/lib/use-guards.decorator-EvI2m2DK.cjs.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
EndpointHandler,
|
|
3
|
+
EndpointOptions,
|
|
4
|
+
RequestArgs,
|
|
5
5
|
} from '@navios/builder'
|
|
6
|
-
import type { z
|
|
6
|
+
import type { z } from 'zod/v4'
|
|
7
7
|
|
|
8
8
|
import { Endpoint as OriginalEndpoint } from '../../decorators/endpoint.decorator.mjs'
|
|
9
9
|
import { createMethodContext } from '../context-compat.mjs'
|
|
@@ -13,22 +13,18 @@ import { createMethodContext } from '../context-compat.mjs'
|
|
|
13
13
|
* Note: In legacy decorators, type constraints are checked when the decorator is applied,
|
|
14
14
|
* but may not be preserved perfectly when decorators are stacked.
|
|
15
15
|
*/
|
|
16
|
-
type EndpointMethodDescriptor<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
|
|
29
|
-
: EndpointFunctionArgs<Url, undefined, undefined, true>,
|
|
30
|
-
) => Promise<z.input<ResponseSchema>>
|
|
31
|
-
>
|
|
16
|
+
type EndpointMethodDescriptor<Config extends EndpointOptions> =
|
|
17
|
+
TypedPropertyDescriptor<
|
|
18
|
+
(
|
|
19
|
+
params: RequestArgs<
|
|
20
|
+
Config['url'],
|
|
21
|
+
Config['querySchema'],
|
|
22
|
+
Config['requestSchema'],
|
|
23
|
+
Config['urlParamsSchema'],
|
|
24
|
+
true
|
|
25
|
+
>,
|
|
26
|
+
) => Promise<z.input<Config['responseSchema']>>
|
|
27
|
+
>
|
|
32
28
|
|
|
33
29
|
/**
|
|
34
30
|
* Legacy-compatible Endpoint decorator.
|
|
@@ -50,30 +46,13 @@ type EndpointMethodDescriptor<
|
|
|
50
46
|
* }
|
|
51
47
|
* ```
|
|
52
48
|
*/
|
|
53
|
-
export function Endpoint<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
QuerySchema = undefined,
|
|
57
|
-
ResponseSchema extends ZodType = ZodType,
|
|
58
|
-
RequestSchema = ZodType,
|
|
59
|
-
>(endpoint: {
|
|
60
|
-
config: BaseEndpointConfig<
|
|
61
|
-
Method,
|
|
62
|
-
Url,
|
|
63
|
-
QuerySchema,
|
|
64
|
-
ResponseSchema,
|
|
65
|
-
RequestSchema
|
|
66
|
-
>
|
|
67
|
-
}) {
|
|
49
|
+
export function Endpoint<const Config extends EndpointOptions>(
|
|
50
|
+
endpoint: EndpointHandler<Config, false>,
|
|
51
|
+
) {
|
|
68
52
|
return function (
|
|
69
53
|
target: any,
|
|
70
54
|
propertyKey: string | symbol,
|
|
71
|
-
descriptor: EndpointMethodDescriptor<
|
|
72
|
-
Url,
|
|
73
|
-
QuerySchema,
|
|
74
|
-
RequestSchema,
|
|
75
|
-
ResponseSchema
|
|
76
|
-
>,
|
|
55
|
+
descriptor: EndpointMethodDescriptor<Config>,
|
|
77
56
|
): PropertyDescriptor | void {
|
|
78
57
|
if (!descriptor) {
|
|
79
58
|
throw new Error(
|
|
@@ -81,12 +60,7 @@ export function Endpoint<
|
|
|
81
60
|
)
|
|
82
61
|
}
|
|
83
62
|
// Type check the descriptor value matches expected signature
|
|
84
|
-
const typedDescriptor = descriptor as EndpointMethodDescriptor<
|
|
85
|
-
Url,
|
|
86
|
-
QuerySchema,
|
|
87
|
-
RequestSchema,
|
|
88
|
-
ResponseSchema
|
|
89
|
-
>
|
|
63
|
+
const typedDescriptor = descriptor as EndpointMethodDescriptor<Config>
|
|
90
64
|
const context = createMethodContext(target, propertyKey, typedDescriptor)
|
|
91
65
|
const originalDecorator = OriginalEndpoint(endpoint)
|
|
92
66
|
// @ts-expect-error - we don't need to type the value
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
EndpointHandler,
|
|
3
|
+
EndpointOptions,
|
|
4
|
+
RequestArgs,
|
|
5
5
|
} from '@navios/builder'
|
|
6
|
-
import type { z
|
|
6
|
+
import type { z } from 'zod/v4'
|
|
7
7
|
|
|
8
8
|
import { Multipart as OriginalMultipart } from '../../decorators/multipart.decorator.mjs'
|
|
9
9
|
import { createMethodContext } from '../context-compat.mjs'
|
|
@@ -13,22 +13,18 @@ import { createMethodContext } from '../context-compat.mjs'
|
|
|
13
13
|
* Note: In legacy decorators, type constraints are checked when the decorator is applied,
|
|
14
14
|
* but may not be preserved perfectly when decorators are stacked.
|
|
15
15
|
*/
|
|
16
|
-
type MultipartMethodDescriptor<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
|
|
29
|
-
: EndpointFunctionArgs<Url, undefined, undefined, true>,
|
|
30
|
-
) => Promise<z.input<ResponseSchema>>
|
|
31
|
-
>
|
|
16
|
+
type MultipartMethodDescriptor<Config extends EndpointOptions> =
|
|
17
|
+
TypedPropertyDescriptor<
|
|
18
|
+
(
|
|
19
|
+
params: RequestArgs<
|
|
20
|
+
Config['url'],
|
|
21
|
+
Config['querySchema'],
|
|
22
|
+
Config['requestSchema'],
|
|
23
|
+
Config['urlParamsSchema'],
|
|
24
|
+
true
|
|
25
|
+
>,
|
|
26
|
+
) => Promise<z.input<Config['responseSchema']>>
|
|
27
|
+
>
|
|
32
28
|
|
|
33
29
|
/**
|
|
34
30
|
* Legacy-compatible Multipart decorator.
|
|
@@ -51,30 +47,13 @@ type MultipartMethodDescriptor<
|
|
|
51
47
|
* }
|
|
52
48
|
* ```
|
|
53
49
|
*/
|
|
54
|
-
export function Multipart<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
QuerySchema = undefined,
|
|
58
|
-
ResponseSchema extends ZodType = ZodType,
|
|
59
|
-
RequestSchema = ZodType,
|
|
60
|
-
>(endpoint: {
|
|
61
|
-
config: BaseEndpointConfig<
|
|
62
|
-
Method,
|
|
63
|
-
Url,
|
|
64
|
-
QuerySchema,
|
|
65
|
-
ResponseSchema,
|
|
66
|
-
RequestSchema
|
|
67
|
-
>
|
|
68
|
-
}) {
|
|
50
|
+
export function Multipart<const Config extends EndpointOptions>(
|
|
51
|
+
endpoint: EndpointHandler<Config, false>,
|
|
52
|
+
) {
|
|
69
53
|
return function <T extends object>(
|
|
70
54
|
target: T,
|
|
71
55
|
propertyKey: string | symbol,
|
|
72
|
-
descriptor: MultipartMethodDescriptor<
|
|
73
|
-
Url,
|
|
74
|
-
QuerySchema,
|
|
75
|
-
RequestSchema,
|
|
76
|
-
ResponseSchema
|
|
77
|
-
>,
|
|
56
|
+
descriptor: MultipartMethodDescriptor<Config>,
|
|
78
57
|
): PropertyDescriptor | void {
|
|
79
58
|
if (!descriptor) {
|
|
80
59
|
throw new Error(
|
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
BaseEndpointOptions,
|
|
3
|
+
RequestArgs,
|
|
4
|
+
StreamHandler,
|
|
5
5
|
} from '@navios/builder'
|
|
6
|
-
import type { ZodObject, ZodType } from 'zod/v4'
|
|
7
6
|
|
|
8
7
|
import { Stream as OriginalStream } from '../../decorators/stream.decorator.mjs'
|
|
9
8
|
import { createMethodContext } from '../context-compat.mjs'
|
|
10
9
|
|
|
11
|
-
type StreamParams<
|
|
12
|
-
Url extends string,
|
|
13
|
-
QuerySchema,
|
|
14
|
-
RequestSchema,
|
|
15
|
-
> = QuerySchema extends ZodObject
|
|
16
|
-
? RequestSchema extends ZodType
|
|
17
|
-
? EndpointFunctionArgs<Url, QuerySchema, RequestSchema, true>
|
|
18
|
-
: EndpointFunctionArgs<Url, QuerySchema, undefined, true>
|
|
19
|
-
: RequestSchema extends ZodType
|
|
20
|
-
? EndpointFunctionArgs<Url, undefined, RequestSchema, true>
|
|
21
|
-
: EndpointFunctionArgs<Url, undefined, undefined, true>
|
|
22
|
-
|
|
23
10
|
/**
|
|
24
11
|
* Type helper to constrain a PropertyDescriptor's value to match a stream endpoint signature.
|
|
25
12
|
* Supports both with and without reply parameter (Bun doesn't use reply parameter).
|
|
13
|
+
* Note: In legacy decorators, type constraints are checked when the decorator is applied,
|
|
14
|
+
* but may not be preserved perfectly when decorators are stacked.
|
|
26
15
|
*/
|
|
27
|
-
type StreamMethodDescriptor<
|
|
28
|
-
Url extends string,
|
|
29
|
-
QuerySchema,
|
|
30
|
-
RequestSchema,
|
|
31
|
-
> =
|
|
16
|
+
type StreamMethodDescriptor<Config extends BaseEndpointOptions> =
|
|
32
17
|
| TypedPropertyDescriptor<
|
|
33
|
-
(
|
|
18
|
+
(
|
|
19
|
+
params: RequestArgs<
|
|
20
|
+
Config['url'],
|
|
21
|
+
Config['querySchema'],
|
|
22
|
+
Config['requestSchema'],
|
|
23
|
+
Config['urlParamsSchema'],
|
|
24
|
+
true
|
|
25
|
+
>,
|
|
26
|
+
reply: any,
|
|
27
|
+
) => any
|
|
34
28
|
>
|
|
35
29
|
| TypedPropertyDescriptor<
|
|
36
|
-
(
|
|
30
|
+
(
|
|
31
|
+
params: RequestArgs<
|
|
32
|
+
Config['url'],
|
|
33
|
+
Config['querySchema'],
|
|
34
|
+
Config['requestSchema'],
|
|
35
|
+
Config['urlParamsSchema'],
|
|
36
|
+
true
|
|
37
|
+
>,
|
|
38
|
+
) => any
|
|
37
39
|
>
|
|
38
|
-
| TypedPropertyDescriptor<() => any>
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Legacy-compatible Stream decorator.
|
|
@@ -58,26 +59,31 @@ type StreamMethodDescriptor<
|
|
|
58
59
|
* }
|
|
59
60
|
* ```
|
|
60
61
|
*/
|
|
61
|
-
export function Stream<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
>(endpoint: {
|
|
67
|
-
config: BaseStreamConfig<Method, Url, QuerySchema, RequestSchema>
|
|
68
|
-
}) {
|
|
69
|
-
return function <T extends object>(
|
|
70
|
-
target: T,
|
|
62
|
+
export function Stream<const Config extends BaseEndpointOptions>(
|
|
63
|
+
endpoint: StreamHandler<Config, false>,
|
|
64
|
+
) {
|
|
65
|
+
return function (
|
|
66
|
+
target: any,
|
|
71
67
|
propertyKey: string | symbol,
|
|
72
|
-
descriptor: StreamMethodDescriptor<
|
|
73
|
-
) {
|
|
74
|
-
|
|
68
|
+
descriptor: StreamMethodDescriptor<Config>,
|
|
69
|
+
): PropertyDescriptor | void {
|
|
70
|
+
if (!descriptor || !descriptor.value) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
'[Navios] @Stream decorator requires a method descriptor. Make sure experimentalDecorators is enabled.',
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
// Type check the descriptor value matches expected signature
|
|
76
|
+
const typedDescriptor = descriptor as StreamMethodDescriptor<Config>
|
|
77
|
+
const context = createMethodContext(target, propertyKey, typedDescriptor)
|
|
75
78
|
const originalDecorator = OriginalStream(endpoint)
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
// Stage3 decorator returns void in type signature but actually returns the function
|
|
80
|
+
// We know value is defined because we checked above
|
|
81
|
+
const result = originalDecorator(typedDescriptor.value!, context) as
|
|
82
|
+
| typeof typedDescriptor.value
|
|
83
|
+
| void
|
|
84
|
+
if (result && result !== typedDescriptor.value) {
|
|
85
|
+
typedDescriptor.value = result as any
|
|
80
86
|
}
|
|
81
|
-
return
|
|
87
|
+
return typedDescriptor
|
|
82
88
|
}
|
|
83
89
|
}
|
|
@@ -14,6 +14,7 @@ export interface ModuleMetadata {
|
|
|
14
14
|
guards: Set<
|
|
15
15
|
ClassTypeWithInstance<CanActivate> | InjectionToken<CanActivate, undefined>
|
|
16
16
|
>
|
|
17
|
+
overrides: Set<ClassType>
|
|
17
18
|
customAttributes: Map<string | symbol, any>
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -35,6 +36,7 @@ export function getModuleMetadata(
|
|
|
35
36
|
| ClassTypeWithInstance<CanActivate>
|
|
36
37
|
| InjectionToken<CanActivate, undefined>
|
|
37
38
|
>(),
|
|
39
|
+
overrides: new Set<ClassType>(),
|
|
38
40
|
customAttributes: new Map<string | symbol, any>(),
|
|
39
41
|
}
|
|
40
42
|
context.metadata[ModuleMetadataKey] = newMetadata
|
|
@@ -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
|
+
}
|