@nice-code/common-errors 0.2.9 → 0.2.11
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/README.md +103 -2
- package/build/hono/index.js +99 -3
- package/build/index.js +60 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,106 @@
|
|
|
1
1
|
# @nice-code/common-errors
|
|
2
2
|
|
|
3
|
-
Shared error domains for Standard Schema validation errors
|
|
3
|
+
Shared error domains for Standard Schema validation errors, with Hono middleware integration.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @nice-code/common-errors
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Peer deps: `valibot` (or any Standard Schema library), `hono` (for `/hono` subpath).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Validation error domain
|
|
16
|
+
|
|
17
|
+
`err_validation` is a [`@nice-code/error`](../nice-error) domain for Standard Schema validation failures. Import it to match or inspect validation errors by domain.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { err_validation, EValidator } from "@nice-code/common-errors";
|
|
21
|
+
|
|
22
|
+
// Check if an error is a validation error
|
|
23
|
+
if (err_validation.isExact(caught)) {
|
|
24
|
+
const hydrated = err_validation.hydrate(caught);
|
|
25
|
+
const { issues } = hydrated.getContext(EValidator.standard_schema);
|
|
26
|
+
// issues: readonly StandardSchemaV1.Issue[]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The domain exposes one error id: `EValidator.standard_schema` with context `{ issues }`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Hono middleware
|
|
35
|
+
|
|
36
|
+
Import from the `/hono` subpath:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { niceSValidator, niceCatchSValidation } from "@nice-code/common-errors/hono";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `niceSValidator(target, schema)`
|
|
43
|
+
|
|
44
|
+
Drop-in replacement for `@hono/standard-validator`'s `sValidator` that throws a `NiceError` instead of returning a 400 response when validation fails.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { niceSValidator } from "@nice-code/common-errors/hono";
|
|
48
|
+
import * as v from "valibot";
|
|
49
|
+
|
|
50
|
+
const CreateUserSchema = v.object({
|
|
51
|
+
name: v.string(),
|
|
52
|
+
email: v.pipe(v.string(), v.email()),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
app.post("/users", niceSValidator("json", CreateUserSchema), async (c) => {
|
|
56
|
+
const body = c.req.valid("json"); // fully typed
|
|
57
|
+
// ...
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
When validation fails, a `NiceError` from the `err_validation` domain is thrown. Use Hono's `onError` handler to convert it to a JSON response:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { castNiceError } from "@nice-code/error";
|
|
65
|
+
|
|
66
|
+
app.onError((err, c) => {
|
|
67
|
+
const niceError = castNiceError(err);
|
|
68
|
+
return c.json(niceError.toJsonObject(), niceError.httpStatusCode as any);
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `niceCatchSValidation()`
|
|
73
|
+
|
|
74
|
+
Middleware that intercepts raw `@hono/standard-validator` validation responses (the default `{ success: false, error: [...] }` shape) and converts them into `NiceError` JSON responses. Use this when you can't replace `sValidator` with `niceSValidator` directly.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
app.use(niceCatchSValidation());
|
|
78
|
+
|
|
79
|
+
// Existing sValidator usage is automatically intercepted
|
|
80
|
+
app.post("/data", sValidator("json", MySchema), handler);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Full Hono example
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { Hono } from "hono";
|
|
89
|
+
import { niceSValidator } from "@nice-code/common-errors/hono";
|
|
90
|
+
import { castNiceError, EErrorPackType } from "@nice-code/error";
|
|
91
|
+
import * as v from "valibot";
|
|
92
|
+
|
|
93
|
+
const app = new Hono();
|
|
94
|
+
|
|
95
|
+
app.onError((err, c) => {
|
|
96
|
+
const niceError = castNiceError(err);
|
|
97
|
+
return c.json(niceError.toJsonObject(), niceError.httpStatusCode as any);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const BodySchema = v.object({ message: v.string() });
|
|
101
|
+
|
|
102
|
+
app.post("/echo", niceSValidator("json", BodySchema), async (c) => {
|
|
103
|
+
const { message } = c.req.valid("json");
|
|
104
|
+
return c.json({ echo: message });
|
|
105
|
+
});
|
|
106
|
+
```
|
package/build/hono/index.js
CHANGED
|
@@ -1,3 +1,99 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// src/validation/err_validation.ts
|
|
2
|
+
import { err, err_nice } from "@nice-code/error";
|
|
3
|
+
import { StatusCodes } from "http-status-codes";
|
|
4
|
+
|
|
5
|
+
// src/validation/standard_schema/extractMessageFromStandardSchema.ts
|
|
6
|
+
function extractPathFromIssue(issue) {
|
|
7
|
+
let pathString = "";
|
|
8
|
+
for (const segment of issue) {
|
|
9
|
+
if (typeof segment === "object") {
|
|
10
|
+
if (segment.key != null) {
|
|
11
|
+
if (typeof segment.key === "number") {
|
|
12
|
+
pathString += `[${String(segment.key)}]`;
|
|
13
|
+
} else if (typeof segment.key === "symbol") {
|
|
14
|
+
pathString += `[SYMBOL:${String(segment.key)}]`;
|
|
15
|
+
} else {
|
|
16
|
+
pathString += `.${String(segment.key)}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
pathString += `.${String(segment)}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return pathString.slice(1);
|
|
24
|
+
}
|
|
25
|
+
var extractMessageFromStandardSchema = (failureResult) => {
|
|
26
|
+
let message = `Data validation failed:
|
|
27
|
+
`;
|
|
28
|
+
let issueCount = 0;
|
|
29
|
+
for (const issue of failureResult.issues) {
|
|
30
|
+
issueCount++;
|
|
31
|
+
if (issue.path == null || issue.path.length === 0) {
|
|
32
|
+
message += ` (issue ${issueCount}) ${issue.message}
|
|
33
|
+
`;
|
|
34
|
+
} else {
|
|
35
|
+
message += ` (issue ${issueCount}) [${extractPathFromIssue(issue.path)}]: ${issue.message}
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return message;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/validation/err_validation.ts
|
|
43
|
+
var EValidator;
|
|
44
|
+
((EValidator2) => {
|
|
45
|
+
EValidator2["standard_schema"] = "standard_schema";
|
|
46
|
+
})(EValidator ||= {});
|
|
47
|
+
var err_validation = err_nice.createChildDomain({
|
|
48
|
+
domain: "err_validation",
|
|
49
|
+
defaultHttpStatusCode: StatusCodes.BAD_REQUEST,
|
|
50
|
+
schema: {
|
|
51
|
+
["standard_schema" /* standard_schema */]: err({
|
|
52
|
+
message: ({ issues }) => extractMessageFromStandardSchema({ issues })
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
// src/hono/niceCatchSValidation.ts
|
|
57
|
+
var niceCatchSValidation = () => async (ctx, next) => {
|
|
58
|
+
await next();
|
|
59
|
+
if (!ctx.res.ok) {
|
|
60
|
+
const clonedResponse = ctx.res.clone();
|
|
61
|
+
const contentType = clonedResponse.headers.get("content-type");
|
|
62
|
+
if (contentType?.includes("application/json")) {
|
|
63
|
+
try {
|
|
64
|
+
const responseJson = await clonedResponse.json();
|
|
65
|
+
if (responseJson["success"] != null && responseJson["error"] != null) {
|
|
66
|
+
console.log("Intercepted JSON:", responseJson);
|
|
67
|
+
const result = responseJson;
|
|
68
|
+
const newError = err_validation.fromId("standard_schema" /* standard_schema */, {
|
|
69
|
+
issues: result.error
|
|
70
|
+
});
|
|
71
|
+
ctx.res = undefined;
|
|
72
|
+
ctx.res = new Response(JSON.stringify(newError.toJsonObject()), {
|
|
73
|
+
status: newError.httpStatusCode,
|
|
74
|
+
headers: {
|
|
75
|
+
"Content-Type": "application/json"
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("Failed to parse response JSON:", error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
// src/hono/niceSValidator.ts
|
|
86
|
+
import { sValidator } from "@hono/standard-validator";
|
|
87
|
+
function niceSValidator(target, schema) {
|
|
88
|
+
return sValidator(target, schema, (result) => {
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
throw err_validation.fromId("standard_schema" /* standard_schema */, {
|
|
91
|
+
issues: result.error
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
niceSValidator,
|
|
98
|
+
niceCatchSValidation
|
|
99
|
+
};
|
package/build/index.js
CHANGED
|
@@ -1,2 +1,60 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
1
|
+
// src/validation/err_validation.ts
|
|
2
|
+
import { err, err_nice } from "@nice-code/error";
|
|
3
|
+
import { StatusCodes } from "http-status-codes";
|
|
4
|
+
|
|
5
|
+
// src/validation/standard_schema/extractMessageFromStandardSchema.ts
|
|
6
|
+
function extractPathFromIssue(issue) {
|
|
7
|
+
let pathString = "";
|
|
8
|
+
for (const segment of issue) {
|
|
9
|
+
if (typeof segment === "object") {
|
|
10
|
+
if (segment.key != null) {
|
|
11
|
+
if (typeof segment.key === "number") {
|
|
12
|
+
pathString += `[${String(segment.key)}]`;
|
|
13
|
+
} else if (typeof segment.key === "symbol") {
|
|
14
|
+
pathString += `[SYMBOL:${String(segment.key)}]`;
|
|
15
|
+
} else {
|
|
16
|
+
pathString += `.${String(segment.key)}`;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
pathString += `.${String(segment)}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return pathString.slice(1);
|
|
24
|
+
}
|
|
25
|
+
var extractMessageFromStandardSchema = (failureResult) => {
|
|
26
|
+
let message = `Data validation failed:
|
|
27
|
+
`;
|
|
28
|
+
let issueCount = 0;
|
|
29
|
+
for (const issue of failureResult.issues) {
|
|
30
|
+
issueCount++;
|
|
31
|
+
if (issue.path == null || issue.path.length === 0) {
|
|
32
|
+
message += ` (issue ${issueCount}) ${issue.message}
|
|
33
|
+
`;
|
|
34
|
+
} else {
|
|
35
|
+
message += ` (issue ${issueCount}) [${extractPathFromIssue(issue.path)}]: ${issue.message}
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return message;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// src/validation/err_validation.ts
|
|
43
|
+
var EValidator;
|
|
44
|
+
((EValidator2) => {
|
|
45
|
+
EValidator2["standard_schema"] = "standard_schema";
|
|
46
|
+
})(EValidator ||= {});
|
|
47
|
+
var err_validation = err_nice.createChildDomain({
|
|
48
|
+
domain: "err_validation",
|
|
49
|
+
defaultHttpStatusCode: StatusCodes.BAD_REQUEST,
|
|
50
|
+
schema: {
|
|
51
|
+
["standard_schema" /* standard_schema */]: err({
|
|
52
|
+
message: ({ issues }) => extractMessageFromStandardSchema({ issues })
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
export {
|
|
57
|
+
extractMessageFromStandardSchema,
|
|
58
|
+
err_validation,
|
|
59
|
+
EValidator
|
|
60
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nice-code/common-errors",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"build-types": "tsc --project tsconfig.build.json"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@nice-code/error": "0.2.
|
|
32
|
+
"@nice-code/error": "0.2.11",
|
|
33
33
|
"@standard-schema/spec": "^1.1.0",
|
|
34
34
|
"@hono/standard-validator": "^0.2.2",
|
|
35
35
|
"http-status-codes": "^2.3.0"
|