@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +1 -1
  3. package/docs/README.md +3 -1
  4. package/docs/exceptions.md +37 -1
  5. package/lib/{index-B2ulzZIr.d.cts → index-BOBeqs9U.d.cts} +445 -2
  6. package/lib/index-BOBeqs9U.d.cts.map +1 -0
  7. package/lib/{index-C8lUQePd.d.mts → index-CVlRKxp2.d.mts} +445 -2
  8. package/lib/index-CVlRKxp2.d.mts.map +1 -0
  9. package/lib/index.cjs +37 -2
  10. package/lib/index.d.cts +2 -2
  11. package/lib/index.d.mts +2 -2
  12. package/lib/index.mjs +2 -2
  13. package/lib/legacy-compat/index.cjs +2 -2
  14. package/lib/legacy-compat/index.cjs.map +1 -1
  15. package/lib/legacy-compat/index.d.cts +7 -5
  16. package/lib/legacy-compat/index.d.cts.map +1 -1
  17. package/lib/legacy-compat/index.d.mts +7 -5
  18. package/lib/legacy-compat/index.d.mts.map +1 -1
  19. package/lib/legacy-compat/index.mjs.map +1 -1
  20. package/lib/{src-Baabu2R8.mjs → src-DqPmV_Qw.mjs} +1743 -26
  21. package/lib/src-DqPmV_Qw.mjs.map +1 -0
  22. package/lib/{src-Cu9OAnfp.cjs → src-dwaFq8LO.cjs} +1948 -171
  23. package/lib/src-dwaFq8LO.cjs.map +1 -0
  24. package/lib/testing/index.cjs +2 -2
  25. package/lib/testing/index.d.cts +1 -1
  26. package/lib/testing/index.d.mts +1 -1
  27. package/lib/testing/index.mjs +1 -1
  28. package/lib/{use-guards.decorator-EvI2m2DK.cjs → use-guards.decorator-C_cBWxyM.cjs} +2 -2
  29. package/lib/{use-guards.decorator-EvI2m2DK.cjs.map → use-guards.decorator-C_cBWxyM.cjs.map} +1 -1
  30. package/package.json +4 -4
  31. package/src/__tests__/responders.spec.mts +339 -0
  32. package/src/index.mts +1 -0
  33. package/src/legacy-compat/__type-tests__/legacy-decorators.spec-d.mts +0 -1
  34. package/src/legacy-compat/attribute.factory.mts +12 -26
  35. package/src/responders/enums/framework-error.enum.mts +38 -0
  36. package/src/responders/enums/index.mts +1 -0
  37. package/src/responders/index.mts +4 -0
  38. package/src/responders/interfaces/error-responder.interface.mts +42 -0
  39. package/src/responders/interfaces/error-response.interface.mts +22 -0
  40. package/src/responders/interfaces/index.mts +3 -0
  41. package/src/responders/interfaces/problem-details.interface.mts +34 -0
  42. package/src/responders/services/error-response-producer.service.mts +143 -0
  43. package/src/responders/services/forbidden-responder.service.mts +77 -0
  44. package/src/responders/services/index.mts +5 -0
  45. package/src/responders/services/internal-server-error-responder.service.mts +57 -0
  46. package/src/responders/services/not-found-responder.service.mts +76 -0
  47. package/src/responders/services/validation-error-responder.service.mts +78 -0
  48. package/src/responders/tokens/index.mts +1 -0
  49. package/src/responders/tokens/responder.tokens.mts +84 -0
  50. package/src/services/guard-runner.service.mts +19 -6
  51. package/lib/index-B2ulzZIr.d.cts.map +0 -1
  52. package/lib/index-C8lUQePd.d.mts.map +0 -1
  53. package/lib/src-Baabu2R8.mjs.map +0 -1
  54. 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 = (() => <T extends ClassType>(
30
- target: T,
31
- ) => T) &
32
- (() => <T extends object>(
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
- ) => <T extends ClassType>(target: T) => T) &
48
- ((
49
- value: z.input<S>,
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,4 @@
1
+ export * from './enums/index.mjs'
2
+ export * from './interfaces/index.mjs'
3
+ export * from './services/index.mjs'
4
+ export * from './tokens/index.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,3 @@
1
+ export * from './error-responder.interface.mjs'
2
+ export * from './error-response.interface.mjs'
3
+ export * from './problem-details.interface.mjs'
@@ -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'