yedra 0.20.11 → 0.20.13
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/dist/routing/rest.d.ts +2 -0
- package/dist/routing/rest.js +2 -0
- package/dist/routing/websocket.d.ts +1 -0
- package/dist/routing/websocket.js +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +7 -0
- package/dist/src/lib.d.ts +10 -0
- package/dist/src/lib.js +16 -0
- package/dist/src/routing/app.d.ts +165 -0
- package/dist/src/routing/app.js +537 -0
- package/dist/src/routing/env.d.ts +4 -0
- package/dist/src/routing/env.js +13 -0
- package/dist/src/routing/errors.d.ts +50 -0
- package/dist/src/routing/errors.js +64 -0
- package/dist/src/routing/log.d.ts +22 -0
- package/dist/src/routing/log.js +30 -0
- package/dist/src/routing/path.d.ts +44 -0
- package/dist/src/routing/path.js +110 -0
- package/dist/src/routing/rest.d.ts +103 -0
- package/dist/src/routing/rest.js +181 -0
- package/dist/src/routing/websocket.d.ts +60 -0
- package/dist/src/routing/websocket.js +132 -0
- package/dist/src/schema-lib.d.ts +17 -0
- package/dist/src/schema-lib.js +20 -0
- package/dist/src/schema.d.ts +2 -0
- package/dist/src/schema.js +4 -0
- package/dist/src/util/counter.d.ts +7 -0
- package/dist/src/util/counter.js +24 -0
- package/dist/src/util/docs.d.ts +9 -0
- package/dist/src/util/docs.js +57 -0
- package/dist/src/util/security.d.ts +14 -0
- package/dist/src/util/security.js +6 -0
- package/dist/src/util/stream.d.ts +2 -0
- package/dist/src/util/stream.js +7 -0
- package/dist/src/validation/body.d.ts +46 -0
- package/dist/src/validation/body.js +15 -0
- package/dist/src/validation/boolean.d.ts +10 -0
- package/dist/src/validation/boolean.js +27 -0
- package/dist/src/validation/date.d.ts +11 -0
- package/dist/src/validation/date.js +29 -0
- package/dist/src/validation/doc.d.ts +10 -0
- package/dist/src/validation/doc.js +22 -0
- package/dist/src/validation/either.d.ts +15 -0
- package/dist/src/validation/either.js +38 -0
- package/dist/src/validation/enum.d.ts +19 -0
- package/dist/src/validation/enum.js +44 -0
- package/dist/src/validation/error.d.ts +21 -0
- package/dist/src/validation/error.js +32 -0
- package/dist/src/validation/integer.d.ts +23 -0
- package/dist/src/validation/integer.js +64 -0
- package/dist/src/validation/json.d.ts +12 -0
- package/dist/src/validation/json.js +33 -0
- package/dist/src/validation/lazy.d.ts +48 -0
- package/dist/src/validation/lazy.js +78 -0
- package/dist/src/validation/modifiable.d.ts +105 -0
- package/dist/src/validation/modifiable.js +223 -0
- package/dist/src/validation/none.d.ts +10 -0
- package/dist/src/validation/none.js +14 -0
- package/dist/src/validation/null.d.ts +10 -0
- package/dist/src/validation/null.js +21 -0
- package/dist/src/validation/number.d.ts +23 -0
- package/dist/src/validation/number.js +58 -0
- package/dist/src/validation/object.d.ts +38 -0
- package/dist/src/validation/object.js +87 -0
- package/dist/src/validation/raw.d.ts +13 -0
- package/dist/src/validation/raw.js +21 -0
- package/dist/src/validation/record.d.ts +16 -0
- package/dist/src/validation/record.js +51 -0
- package/dist/src/validation/schema.d.ts +35 -0
- package/dist/src/validation/schema.js +76 -0
- package/dist/src/validation/stream.d.ts +13 -0
- package/dist/src/validation/stream.js +26 -0
- package/dist/src/validation/string.d.ts +29 -0
- package/dist/src/validation/string.js +51 -0
- package/dist/src/validation/union.d.ts +16 -0
- package/dist/src/validation/union.js +36 -0
- package/dist/src/validation/unknown.d.ts +10 -0
- package/dist/src/validation/unknown.js +13 -0
- package/dist/src/validation/uuid.d.ts +11 -0
- package/dist/src/validation/uuid.js +23 -0
- package/package.json +3 -4
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Browser-safe schema-only exports.
|
|
2
|
+
// This module excludes all Node.js-specific code (HTTP server,
|
|
3
|
+
// WebSocket, streams, etc.) so it can be safely bundled for the browser.
|
|
4
|
+
export { BodyType } from './validation/body.js';
|
|
5
|
+
export { boolean } from './validation/boolean.js';
|
|
6
|
+
export { date } from './validation/date.js';
|
|
7
|
+
export { _enum as enum } from './validation/enum.js';
|
|
8
|
+
export { ValidationError } from './validation/error.js';
|
|
9
|
+
export { integer } from './validation/integer.js';
|
|
10
|
+
export { lazy, LazySchema } from './validation/lazy.js';
|
|
11
|
+
export { array } from './validation/modifiable.js';
|
|
12
|
+
export { _null as null } from './validation/null.js';
|
|
13
|
+
export { number } from './validation/number.js';
|
|
14
|
+
export { laxObject, object } from './validation/object.js';
|
|
15
|
+
export { record } from './validation/record.js';
|
|
16
|
+
export { Schema } from './validation/schema.js';
|
|
17
|
+
export { string } from './validation/string.js';
|
|
18
|
+
export { union } from './validation/union.js';
|
|
19
|
+
export { unknown } from './validation/unknown.js';
|
|
20
|
+
export { uuid } from './validation/uuid.js';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class Counter {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.count = 0;
|
|
4
|
+
}
|
|
5
|
+
wait() {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
if (this.count === 0) {
|
|
8
|
+
resolve();
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
this.resolve = resolve;
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
increment() {
|
|
16
|
+
this.count += 1;
|
|
17
|
+
}
|
|
18
|
+
decrement() {
|
|
19
|
+
this.count -= 1;
|
|
20
|
+
if (this.count === 0 && this.resolve !== undefined) {
|
|
21
|
+
this.resolve();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Schema } from '../validation/schema.js';
|
|
2
|
+
import type { SecurityScheme } from './security.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generate OpenAPI documentation for parameters.
|
|
5
|
+
* @param params - Record of parameters.
|
|
6
|
+
* @param position - The position of the parameter, e.g. `path`, `query`, `header`. This is passed directly to OpenAPI.
|
|
7
|
+
* @returns A list of parameter documentations in OpenAPI format.
|
|
8
|
+
*/
|
|
9
|
+
export declare const paramDocs: <Params extends Record<string, Schema<unknown>>>(params: Params, position: "path" | "query" | "header", security: SecurityScheme[]) => object[];
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate OpenAPI documentation for parameters.
|
|
3
|
+
* @param params - Record of parameters.
|
|
4
|
+
* @param position - The position of the parameter, e.g. `path`, `query`, `header`. This is passed directly to OpenAPI.
|
|
5
|
+
* @returns A list of parameter documentations in OpenAPI format.
|
|
6
|
+
*/
|
|
7
|
+
export const paramDocs = (params, position, security) => {
|
|
8
|
+
const result = [];
|
|
9
|
+
for (const name in params) {
|
|
10
|
+
if (isAuthToken(name, position, security)) {
|
|
11
|
+
// don't include auth tokens in the documentation, they're already
|
|
12
|
+
// handled by the separate authentication feature of OpenAPI
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const docs = params[name].documentation();
|
|
16
|
+
result.push({
|
|
17
|
+
name,
|
|
18
|
+
in: position,
|
|
19
|
+
description: 'description' in docs ? docs.description : undefined,
|
|
20
|
+
required: !params[name].isOptional(),
|
|
21
|
+
schema: docs,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Check whether this parameter is an auth token, and should therefore not be
|
|
28
|
+
* included directly in the generated documentation.
|
|
29
|
+
* @param paramName - The name of the parameter to check.
|
|
30
|
+
* @param position - The position of the parameter.
|
|
31
|
+
* @param security - The security schemes that are enabled for this endpoint.
|
|
32
|
+
* @param securitySchemes - The security scheme definitions.
|
|
33
|
+
* @returns Whether the parameter is an auth token.
|
|
34
|
+
*/
|
|
35
|
+
const isAuthToken = (paramName, position, securitySchemes) => {
|
|
36
|
+
if (position === 'path') {
|
|
37
|
+
// path parameters cannot be auth tokens
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
for (const scheme of securitySchemes) {
|
|
41
|
+
if (scheme.scheme.type === 'http') {
|
|
42
|
+
if (paramName.toLowerCase() === 'authorization' &&
|
|
43
|
+
position === 'header') {
|
|
44
|
+
// http auth token has to be in authorization header
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (scheme.scheme.in === position &&
|
|
50
|
+
paramName.toLowerCase() === scheme.scheme.name.toLowerCase()) {
|
|
51
|
+
// API key has to have the correct name and be in the correct position
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class SecurityScheme {
|
|
2
|
+
name: string;
|
|
3
|
+
scheme: SecuritySchemeData;
|
|
4
|
+
constructor(name: string, scheme: SecuritySchemeData);
|
|
5
|
+
}
|
|
6
|
+
type SecuritySchemeData = {
|
|
7
|
+
type: 'http';
|
|
8
|
+
scheme: 'basic' | 'bearer';
|
|
9
|
+
} | {
|
|
10
|
+
type: 'apiKey';
|
|
11
|
+
in: 'header' | 'query' | 'cookie';
|
|
12
|
+
name: string;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
/**
|
|
3
|
+
* The base class for all body types.
|
|
4
|
+
* @typeParam Provides - The type that is provided by this body type.
|
|
5
|
+
* When a parse is successful, this is the type of object you get back.
|
|
6
|
+
* @typeParam Accepts - The type that is accepted by this body type.
|
|
7
|
+
* When you want a parse to be successful, this is the type of object
|
|
8
|
+
* you have to pass in. This is always weaker than `Provides`, since you
|
|
9
|
+
* can always pass in a value of `Provides` and get a success.
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class BodyType<Provides, Accepts> {
|
|
12
|
+
_provides: Provides;
|
|
13
|
+
_accepts: Accepts;
|
|
14
|
+
constructor();
|
|
15
|
+
/**
|
|
16
|
+
* Deserialize a raw stream.
|
|
17
|
+
* @param stream - The raw stream.
|
|
18
|
+
* @param contentType - The content type.
|
|
19
|
+
*/
|
|
20
|
+
abstract deserialize(stream: Readable, contentType: string): Promise<Provides>;
|
|
21
|
+
/**
|
|
22
|
+
* Generate OpenAPI docs for this body.
|
|
23
|
+
*/
|
|
24
|
+
abstract bodyDocs(): object;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get the type that is provided by a schema. When you parse
|
|
28
|
+
* something with a schema, this is the type you get back.
|
|
29
|
+
*/
|
|
30
|
+
export type TypeofProvides<T extends BodyType<unknown, unknown>> = T['_provides'];
|
|
31
|
+
/**
|
|
32
|
+
* Get the type that is accepted by a schema. When you parse
|
|
33
|
+
* something with a schema, this is the type you have to pass
|
|
34
|
+
* in to get a successful result.
|
|
35
|
+
*/
|
|
36
|
+
export type TypeofAccepts<T extends BodyType<unknown, unknown>> = T['_accepts'];
|
|
37
|
+
/**
|
|
38
|
+
* Get the type that is parsed by a schema. This is the same
|
|
39
|
+
* as `TypeofProvides`.
|
|
40
|
+
*
|
|
41
|
+
* ```typescript
|
|
42
|
+
* const schema = y.string();
|
|
43
|
+
* type SchemaType = y.Typeof<typeof schema>; // string
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export type Typeof<T extends BodyType<unknown, unknown>> = TypeofProvides<T>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The base class for all body types.
|
|
3
|
+
* @typeParam Provides - The type that is provided by this body type.
|
|
4
|
+
* When a parse is successful, this is the type of object you get back.
|
|
5
|
+
* @typeParam Accepts - The type that is accepted by this body type.
|
|
6
|
+
* When you want a parse to be successful, this is the type of object
|
|
7
|
+
* you have to pass in. This is always weaker than `Provides`, since you
|
|
8
|
+
* can always pass in a value of `Provides` and get a success.
|
|
9
|
+
*/
|
|
10
|
+
export class BodyType {
|
|
11
|
+
constructor() {
|
|
12
|
+
this._provides = undefined;
|
|
13
|
+
this._accepts = undefined;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ModifiableSchema } from './modifiable.js';
|
|
2
|
+
declare class BooleanSchema extends ModifiableSchema<boolean> {
|
|
3
|
+
parse(obj: unknown): boolean;
|
|
4
|
+
documentation(): object;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A schema matching any boolean.
|
|
8
|
+
*/
|
|
9
|
+
export declare const boolean: () => BooleanSchema;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Issue, ValidationError } from './error.js';
|
|
2
|
+
import { ModifiableSchema } from './modifiable.js';
|
|
3
|
+
class BooleanSchema extends ModifiableSchema {
|
|
4
|
+
parse(obj) {
|
|
5
|
+
if (obj === 'true') {
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
if (obj === 'false') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (typeof obj !== 'boolean') {
|
|
12
|
+
throw new ValidationError([
|
|
13
|
+
new Issue([], `Expected boolean but got ${typeof obj}`),
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
return obj;
|
|
17
|
+
}
|
|
18
|
+
documentation() {
|
|
19
|
+
return {
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A schema matching any boolean.
|
|
26
|
+
*/
|
|
27
|
+
export const boolean = () => new BooleanSchema();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ModifiableSchema } from './modifiable.js';
|
|
2
|
+
declare class DateSchema extends ModifiableSchema<Date> {
|
|
3
|
+
parse(obj: unknown): Date;
|
|
4
|
+
documentation(): object;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* A schema matching date objects, or strings and numbers that can be
|
|
8
|
+
* interpreted as dates.
|
|
9
|
+
*/
|
|
10
|
+
export declare const date: () => DateSchema;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Issue, ValidationError } from './error.js';
|
|
2
|
+
import { ModifiableSchema } from './modifiable.js';
|
|
3
|
+
class DateSchema extends ModifiableSchema {
|
|
4
|
+
parse(obj) {
|
|
5
|
+
if (obj instanceof Date) {
|
|
6
|
+
return obj;
|
|
7
|
+
}
|
|
8
|
+
if (typeof obj === 'string' || typeof obj === 'number') {
|
|
9
|
+
const date = new Date(obj);
|
|
10
|
+
if (date.toString() !== 'Invalid Date') {
|
|
11
|
+
return date;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
throw new ValidationError([
|
|
15
|
+
new Issue([], `Expected date but got ${typeof obj}`),
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
documentation() {
|
|
19
|
+
return {
|
|
20
|
+
type: 'string',
|
|
21
|
+
format: 'date-time',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A schema matching date objects, or strings and numbers that can be
|
|
27
|
+
* interpreted as dates.
|
|
28
|
+
*/
|
|
29
|
+
export const date = () => new DateSchema();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Schema } from './schema.js';
|
|
2
|
+
export declare class DocSchema<T> extends Schema<T> {
|
|
3
|
+
private readonly _schema;
|
|
4
|
+
private readonly _description;
|
|
5
|
+
private readonly _example?;
|
|
6
|
+
constructor(schema: Schema<T>, description: string, example?: T);
|
|
7
|
+
parse(obj: unknown): T;
|
|
8
|
+
documentation(): object;
|
|
9
|
+
isOptional(): boolean;
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Schema } from './schema.js';
|
|
2
|
+
export class DocSchema extends Schema {
|
|
3
|
+
constructor(schema, description, example) {
|
|
4
|
+
super();
|
|
5
|
+
this._schema = schema;
|
|
6
|
+
this._description = description;
|
|
7
|
+
this._example = example;
|
|
8
|
+
}
|
|
9
|
+
parse(obj) {
|
|
10
|
+
return this._schema.parse(obj);
|
|
11
|
+
}
|
|
12
|
+
documentation() {
|
|
13
|
+
return {
|
|
14
|
+
description: this._description,
|
|
15
|
+
...(this._example !== undefined && { example: this._example }),
|
|
16
|
+
...this._schema.documentation(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
isOptional() {
|
|
20
|
+
return this._schema.isOptional();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import { BodyType, type Typeof, type TypeofAccepts, type TypeofProvides } from './body.js';
|
|
3
|
+
declare class EitherBody<T extends [...BodyType<unknown, unknown>[]]> extends BodyType<TypeofProvides<T[number]>, TypeofAccepts<T[number]>> {
|
|
4
|
+
private options;
|
|
5
|
+
constructor(options: T);
|
|
6
|
+
deserialize(stream: Readable, contentType: string): Promise<Typeof<T[number]>>;
|
|
7
|
+
bodyDocs(): object;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A body that matches any of the provided options.
|
|
11
|
+
* @param options
|
|
12
|
+
* @returns
|
|
13
|
+
*/
|
|
14
|
+
export declare const either: <T extends [...BodyType<unknown, unknown>[]]>(...options: T) => EitherBody<T>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { BodyType, } from './body.js';
|
|
2
|
+
import { ValidationError } from './error.js';
|
|
3
|
+
class EitherBody extends BodyType {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super();
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
deserialize(stream, contentType) {
|
|
9
|
+
const issues = [];
|
|
10
|
+
for (const option of this.options) {
|
|
11
|
+
try {
|
|
12
|
+
return option.deserialize(stream, contentType);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error instanceof ValidationError) {
|
|
16
|
+
issues.push(...error.issues);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
throw new ValidationError(issues);
|
|
24
|
+
}
|
|
25
|
+
bodyDocs() {
|
|
26
|
+
let docs = {};
|
|
27
|
+
for (const option of this.options) {
|
|
28
|
+
docs = { ...docs, ...option.bodyDocs() };
|
|
29
|
+
}
|
|
30
|
+
return docs;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A body that matches any of the provided options.
|
|
35
|
+
* @param options
|
|
36
|
+
* @returns
|
|
37
|
+
*/
|
|
38
|
+
export const either = (...options) => new EitherBody(options);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ModifiableSchema } from './modifiable.js';
|
|
2
|
+
declare class EnumSchema<T extends [...(string | number)[]]> extends ModifiableSchema<T[number]> {
|
|
3
|
+
private readonly options;
|
|
4
|
+
private readonly normalized;
|
|
5
|
+
constructor(options: T);
|
|
6
|
+
parse(obj: unknown): T[number];
|
|
7
|
+
documentation(): object;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A schema that matches exactly the values provided. The options provided can
|
|
11
|
+
* be either strings or numbers.
|
|
12
|
+
*
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const options = y.enum(3, 4, 'hello');
|
|
15
|
+
* type OptionsType = y.Typeof<typeof options>; // 3 | 4 | 'hello'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare const _enum: <T extends [...(string | number)[]]>(...options: T) => EnumSchema<T>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Issue, ValidationError } from './error.js';
|
|
2
|
+
import { ModifiableSchema } from './modifiable.js';
|
|
3
|
+
class EnumSchema extends ModifiableSchema {
|
|
4
|
+
constructor(options) {
|
|
5
|
+
super();
|
|
6
|
+
this.options = options;
|
|
7
|
+
this.normalized = options.map((option) => option.toString());
|
|
8
|
+
}
|
|
9
|
+
parse(obj) {
|
|
10
|
+
if (typeof obj !== 'string' && typeof obj !== 'number') {
|
|
11
|
+
// enum objects can only be strings or numbers
|
|
12
|
+
throw new ValidationError([
|
|
13
|
+
new Issue([], `Expected one of ${this.options.join(', ')} but got ${typeof obj}`),
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
// compare only the stringified (normalized) values
|
|
17
|
+
const normalizedObj = obj.toString();
|
|
18
|
+
const index = this.normalized.indexOf(normalizedObj);
|
|
19
|
+
if (index === -1) {
|
|
20
|
+
// invalid value
|
|
21
|
+
throw new ValidationError([
|
|
22
|
+
new Issue([], `Expected one of ${this.options.join(', ')} but got ${obj}`),
|
|
23
|
+
]);
|
|
24
|
+
}
|
|
25
|
+
// return the un-normalized value
|
|
26
|
+
return this.options[index];
|
|
27
|
+
}
|
|
28
|
+
documentation() {
|
|
29
|
+
return {
|
|
30
|
+
type: 'string',
|
|
31
|
+
enum: this.options,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A schema that matches exactly the values provided. The options provided can
|
|
37
|
+
* be either strings or numbers.
|
|
38
|
+
*
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const options = y.enum(3, 4, 'hello');
|
|
41
|
+
* type OptionsType = y.Typeof<typeof options>; // 3 | 4 | 'hello'
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export const _enum = (...options) => new EnumSchema(options);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare class Issue {
|
|
2
|
+
readonly path: string[];
|
|
3
|
+
readonly message: string;
|
|
4
|
+
constructor(path: string[], message: string);
|
|
5
|
+
}
|
|
6
|
+
export declare class ValidationError extends Error {
|
|
7
|
+
readonly issues: Issue[];
|
|
8
|
+
constructor(issues: Issue[]);
|
|
9
|
+
/**
|
|
10
|
+
* Format the validation error as a string.
|
|
11
|
+
* @returns The formatted error.
|
|
12
|
+
*/
|
|
13
|
+
format(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Adds a prefix to the validation error paths.
|
|
16
|
+
* @param prefix - The prefix path.
|
|
17
|
+
* @returns The prefixed error.
|
|
18
|
+
*/
|
|
19
|
+
withPrefix(prefix: string): Issue[];
|
|
20
|
+
private static formatIssues;
|
|
21
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class Issue {
|
|
2
|
+
constructor(path, message) {
|
|
3
|
+
this.path = path;
|
|
4
|
+
this.message = message;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class ValidationError extends Error {
|
|
8
|
+
constructor(issues) {
|
|
9
|
+
super(ValidationError.formatIssues(issues));
|
|
10
|
+
this.issues = issues;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Format the validation error as a string.
|
|
14
|
+
* @returns The formatted error.
|
|
15
|
+
*/
|
|
16
|
+
format() {
|
|
17
|
+
return ValidationError.formatIssues(this.issues);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Adds a prefix to the validation error paths.
|
|
21
|
+
* @param prefix - The prefix path.
|
|
22
|
+
* @returns The prefixed error.
|
|
23
|
+
*/
|
|
24
|
+
withPrefix(prefix) {
|
|
25
|
+
return this.issues.map((issue) => new Issue([prefix, ...issue.path], issue.message));
|
|
26
|
+
}
|
|
27
|
+
static formatIssues(issues) {
|
|
28
|
+
return issues
|
|
29
|
+
.map((issue) => `Error at \`${issue.path.join('.')}\`: ${issue.message}.`)
|
|
30
|
+
.join(' ');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ModifiableSchema } from './modifiable.js';
|
|
2
|
+
declare class IntegerSchema extends ModifiableSchema<number> {
|
|
3
|
+
private readonly minValue?;
|
|
4
|
+
private readonly maxValue?;
|
|
5
|
+
constructor(min?: number, max?: number);
|
|
6
|
+
/**
|
|
7
|
+
* Set the minimum value the number is allowed to be.
|
|
8
|
+
* @param value - The minimum value.
|
|
9
|
+
*/
|
|
10
|
+
min(value: number): IntegerSchema;
|
|
11
|
+
/**
|
|
12
|
+
* Set the maximum value the number is allowed to be.
|
|
13
|
+
* @param value - The maximum value.
|
|
14
|
+
*/
|
|
15
|
+
max(value: number): IntegerSchema;
|
|
16
|
+
parse(obj: unknown): number;
|
|
17
|
+
documentation(): object;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A schema that matches an integer.
|
|
21
|
+
*/
|
|
22
|
+
export declare const integer: () => IntegerSchema;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Issue, ValidationError } from './error.js';
|
|
2
|
+
import { ModifiableSchema } from './modifiable.js';
|
|
3
|
+
class IntegerSchema extends ModifiableSchema {
|
|
4
|
+
constructor(min, max) {
|
|
5
|
+
super();
|
|
6
|
+
this.minValue = min;
|
|
7
|
+
this.maxValue = max;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Set the minimum value the number is allowed to be.
|
|
11
|
+
* @param value - The minimum value.
|
|
12
|
+
*/
|
|
13
|
+
min(value) {
|
|
14
|
+
if (!Number.isInteger(value)) {
|
|
15
|
+
throw new Error('minimum value has to be an integer');
|
|
16
|
+
}
|
|
17
|
+
return new IntegerSchema(value, this.maxValue);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Set the maximum value the number is allowed to be.
|
|
21
|
+
* @param value - The maximum value.
|
|
22
|
+
*/
|
|
23
|
+
max(value) {
|
|
24
|
+
if (!Number.isInteger(value)) {
|
|
25
|
+
throw new Error('maximum value has to be an integer');
|
|
26
|
+
}
|
|
27
|
+
return new IntegerSchema(this.minValue, value);
|
|
28
|
+
}
|
|
29
|
+
parse(obj) {
|
|
30
|
+
if (typeof obj !== 'number' && typeof obj !== 'string') {
|
|
31
|
+
throw new ValidationError([
|
|
32
|
+
new Issue([], `Expected number but got ${typeof obj}`),
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
const num = typeof obj === 'number' ? obj : Number.parseFloat(obj);
|
|
36
|
+
if (Number.isNaN(num) || !Number.isInteger(num)) {
|
|
37
|
+
throw new ValidationError([
|
|
38
|
+
new Issue([], `Expected integer but got ${typeof obj}`),
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
if (this.minValue !== undefined && num < this.minValue) {
|
|
42
|
+
throw new ValidationError([
|
|
43
|
+
new Issue([], `Must be at least ${this.minValue}, but was ${num}`),
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
if (this.maxValue !== undefined && num > this.maxValue) {
|
|
47
|
+
throw new ValidationError([
|
|
48
|
+
new Issue([], `Must be at most ${this.maxValue}, but was ${num}`),
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
return num;
|
|
52
|
+
}
|
|
53
|
+
documentation() {
|
|
54
|
+
return {
|
|
55
|
+
type: 'integer',
|
|
56
|
+
...(this.minValue !== undefined && { minimum: this.minValue }),
|
|
57
|
+
...(this.maxValue !== undefined && { maximum: this.maxValue }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A schema that matches an integer.
|
|
63
|
+
*/
|
|
64
|
+
export const integer = () => new IntegerSchema();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Readable } from 'node:stream';
|
|
2
|
+
import { BodyType } from './body.js';
|
|
3
|
+
import type { Schema } from './schema.js';
|
|
4
|
+
declare class JsonBody<T> extends BodyType<T, T> {
|
|
5
|
+
private contentType;
|
|
6
|
+
private schema;
|
|
7
|
+
constructor(contentType: string, schema: Schema<T>);
|
|
8
|
+
deserialize(stream: Readable, contentType: string): Promise<T>;
|
|
9
|
+
bodyDocs(): object;
|
|
10
|
+
}
|
|
11
|
+
export declare const json: <T>(schema: Schema<T>, contentType: string) => JsonBody<T>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readableToBuffer } from '../util/stream.js';
|
|
2
|
+
import { BodyType } from './body.js';
|
|
3
|
+
import { Issue, ValidationError } from './error.js';
|
|
4
|
+
class JsonBody extends BodyType {
|
|
5
|
+
constructor(contentType, schema) {
|
|
6
|
+
super();
|
|
7
|
+
this.contentType = contentType;
|
|
8
|
+
this.schema = schema;
|
|
9
|
+
}
|
|
10
|
+
async deserialize(stream, contentType) {
|
|
11
|
+
const buffer = await readableToBuffer(stream);
|
|
12
|
+
if (buffer.length === 0) {
|
|
13
|
+
return this.schema.parse({});
|
|
14
|
+
}
|
|
15
|
+
if (contentType !== this.contentType) {
|
|
16
|
+
throw new ValidationError([
|
|
17
|
+
new Issue([], `Expected content type \`${this.contentType}\`, but got \`${contentType}\``),
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
const obj = JSON.parse(buffer.toString('utf-8'));
|
|
21
|
+
return this.schema.parse(obj);
|
|
22
|
+
}
|
|
23
|
+
bodyDocs() {
|
|
24
|
+
return {
|
|
25
|
+
[this.contentType]: {
|
|
26
|
+
schema: this.schema.documentation(),
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export const json = (schema, contentType) => {
|
|
32
|
+
return new JsonBody(contentType, schema);
|
|
33
|
+
};
|