Skip to content

Commit 8d4e0ea

Browse files
committed
Revert Standard Schema Issue Mapping
1 parent 4cffe1e commit 8d4e0ea

3 files changed

Lines changed: 189 additions & 77 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/** The Standard Schema interface. */
2+
export interface StandardSchemaV1<Input = unknown, Output = Input> {
3+
/** The Standard Schema properties. */
4+
readonly "~standard": StandardSchemaV1.Props<Input, Output>;
5+
}
6+
7+
export declare namespace StandardSchemaV1 {
8+
/** The Standard Schema properties interface. */
9+
export interface Props<Input = unknown, Output = Input> {
10+
/** The version number of the standard. */
11+
readonly version: 1;
12+
/** The vendor name of the schema library. */
13+
readonly vendor: string;
14+
/** Validates unknown input values. */
15+
readonly validate: (
16+
value: unknown,
17+
) => Result<Output> | Promise<Result<Output>>;
18+
/** Inferred types associated with the schema. */
19+
readonly types?: Types<Input, Output> | undefined;
20+
}
21+
22+
/** The result interface of the validate function. */
23+
export type Result<Output> = SuccessResult<Output> | FailureResult;
24+
25+
/** The result interface if validation succeeds. */
26+
export interface SuccessResult<Output> {
27+
/** The typed output value. */
28+
readonly value: Output;
29+
/** The non-existent issues. */
30+
readonly issues?: undefined;
31+
}
32+
33+
/** The result interface if validation fails. */
34+
export interface FailureResult {
35+
/** The issues of failed validation. */
36+
readonly issues: ReadonlyArray<Issue>;
37+
}
38+
39+
/** The issue interface of the failure output. */
40+
export interface Issue {
41+
/** The error message of the issue. */
42+
readonly message: string;
43+
/** The path of the issue, if any. */
44+
readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
45+
}
46+
47+
/** The path segment interface of the issue. */
48+
export interface PathSegment {
49+
/** The key representing a path segment. */
50+
readonly key: PropertyKey;
51+
}
52+
53+
/** The Standard Schema types interface. */
54+
export interface Types<Input = unknown, Output = Input> {
55+
/** The input type of the schema. */
56+
readonly input: Input;
57+
/** The output type of the schema. */
58+
readonly output: Output;
59+
}
60+
61+
/** Infers the input type of a Standard Schema. */
62+
export type InferInput<Schema extends StandardSchemaV1> = NonNullable<
63+
Schema["~standard"]["types"]
64+
>["input"];
65+
66+
/** Infers the output type of a Standard Schema. */
67+
export type InferOutput<Schema extends StandardSchemaV1> = NonNullable<
68+
Schema["~standard"]["types"]
69+
>["output"];
70+
71+
// biome-ignore lint/complexity/noUselessEmptyExport: needed for granular visibility control of TS namespace
72+
export {};
73+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*--------------------------------------------------------------------------
2+
3+
TypeBox
4+
5+
The MIT License (MIT)
6+
7+
Copyright (c) 2017-2025 Haydn Paterson
8+
9+
Permission is hereby granted, free of charge, to any person obtaining a copy
10+
of this software and associated documentation files (the "Software"), to deal
11+
in the Software without restriction, including without limitation the rights
12+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
copies of the Software, and to permit persons to whom the Software is
14+
furnished to do so, subject to the following conditions:
15+
16+
The above copyright notice and this permission notice shall be included in
17+
all copies or substantial portions of the Software.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
27+
---------------------------------------------------------------------------*/
28+
29+
import { type TLocalizedValidationError, type TValidationErrorBase, type TStandardSchemaV1Error, IsLocalizedValidationError } from 'typebox/error'
30+
import { StandardSchemaV1 } from './standard-schema-v1.ts'
31+
import Guard from 'typebox/guard'
32+
33+
// --------------------------------------------------------
34+
// StandardSchema: PathSegments
35+
// --------------------------------------------------------
36+
function PathSegments(pointer: string): string[] {
37+
if (Guard.IsEqual(pointer.length, 0)) return []
38+
return pointer.slice(1).split("/").map(segment => segment.replace(/~1/g, "/").replace(/~0/g, "~"))
39+
}
40+
// --------------------------------------------------------
41+
// IsStandardSchemaV1Error
42+
// --------------------------------------------------------
43+
function IsStandardSchemaV1Error(error: TValidationErrorBase): error is TStandardSchemaV1Error {
44+
return Guard.IsEqual(error.keyword, '~standard')
45+
}
46+
// --------------------------------------------------------
47+
// IssuesFromLocalizedError
48+
// --------------------------------------------------------
49+
function IssuesFromStandardSchemaV1Error(error: TStandardSchemaV1Error): StandardSchemaV1.Issue[] {
50+
const leading = PathSegments(error.instancePath)
51+
const issues = Guard.IsArray(error.params.issues) ? error.params.issues : []
52+
return issues.map(issue => {
53+
const message = Guard.IsString(issue.message) ? issue.message : 'unknown'
54+
const path = Guard.IsArray(issue.path) ? [...leading, ...issue.path] : leading
55+
return { message, path }
56+
})
57+
}
58+
function IssuesFromRegularError(error: TLocalizedValidationError): StandardSchemaV1.Issue[] {
59+
const path = PathSegments(error.instancePath)
60+
return [{ path, message: error.message }]
61+
}
62+
function IssuesFromLocalizedError(error: TLocalizedValidationError): StandardSchemaV1.Issue[] {
63+
return IsStandardSchemaV1Error(error)
64+
? IssuesFromStandardSchemaV1Error(error)
65+
: IssuesFromRegularError(error)
66+
}
67+
// --------------------------------------------------------
68+
// IssuesFromUnknown
69+
// --------------------------------------------------------
70+
function IssuesFromUnknown(error: object): StandardSchemaV1.Issue[] {
71+
const path = Guard.HasPropertyKey(error, 'path') && Guard.IsArray(error.path) && error.path.every(segment => Guard.IsString(segment)) ? error.path : []
72+
const message = Guard.HasPropertyKey(error, 'message') && Guard.IsString(error.message) ? error.message : 'unknown'
73+
return [{ path, message } as StandardSchemaV1.Issue]
74+
}
75+
// --------------------------------------------------------
76+
// CreateIssues
77+
// --------------------------------------------------------
78+
function FromError(error: object): StandardSchemaV1.Issue[] {
79+
return IsLocalizedValidationError(error)
80+
? IssuesFromLocalizedError(error)
81+
: IssuesFromUnknown(error)
82+
}
83+
// --------------------------------------------------------
84+
// ToStandardSchemaV1Issues
85+
// --------------------------------------------------------
86+
export type TErrorLike = TValidationErrorBase | StandardSchemaV1.Issue
87+
88+
/** Transforms an array of ErrorLikes into a consistent StandardSchemaV1.Issue[] array. */
89+
export function ToStandardSchemaV1Issues(errorLikes: TErrorLike[]): StandardSchemaV1.Issue[] {
90+
return errorLikes.reduce<StandardSchemaV1.Issue[]>((result, error) => {
91+
return [...result, ...FromError(error)]
92+
}, [])
93+
}

src/type/types/base.ts

Lines changed: 23 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ THE SOFTWARE.
2828

2929
// deno-fmt-ignore-file
3030

31-
import { type TLocalizedValidationError, type TStandardSchemaV1Error, type TValidationErrorBase, IsLocalizedValidationError } from '../../error/index.ts'
32-
import { Guard } from '../../guard/index.ts'
3331
import { type TSchema, IsKind } from './schema.ts'
3432

3533
// ------------------------------------------------------------------
@@ -47,32 +45,8 @@ export class BaseNotImplemented extends Error {
4745
})
4846
}
4947
}
50-
// ------------------------------------------------------------------------------------
51-
// BaseValidator
52-
// ------------------------------------------------------------------------------------
53-
class BaseValidator<Value extends unknown = unknown> implements StandardSchemaV1.Props<Value> {
54-
public readonly vendor = 'typebox'
55-
public readonly version = 1
56-
constructor(
57-
private readonly check: (value: unknown) => boolean,
58-
private readonly errors: (value: unknown) => object[]
59-
) { }
60-
public readonly validate = (value: unknown): StandardSchemaV1.Result<Value> => {
61-
return this.check(value)
62-
? this.Success(value as Value)
63-
: this.Failure(this.errors(value))
64-
}
65-
private Success(value: Value): StandardSchemaV1.SuccessResult<Value> {
66-
return { value }
67-
}
68-
private Failure(errors: object[]): StandardSchemaV1.FailureResult {
69-
const issues = errors.reduce<StandardSchemaV1.Issue[]>((result, error) =>
70-
[...result, ...CreateIssues(error)], [])
71-
return { issues } as never
72-
}
73-
}
7448
// ------------------------------------------------------------------
75-
// Type
49+
// Type.Base<...>
7650
// ------------------------------------------------------------------
7751
/** Base class for creating extension types. */
7852
export class Base<Value extends unknown = unknown> implements TSchema {
@@ -123,58 +97,30 @@ export class Base<Value extends unknown = unknown> implements TSchema {
12397
export function IsBase(value: unknown): value is Base {
12498
return IsKind(value, 'Base')
12599
}
126-
// --------------------------------------------------------
127-
// StandardSchema: PathSegments
128-
// --------------------------------------------------------
129-
function PathSegments(pointer: string): string[] {
130-
if (Guard.IsEqual(pointer.length, 0)) return []
131-
return pointer.slice(1).split("/").map(segment => segment.replace(/~1/g, "/").replace(/~0/g, "~"))
132-
}
133-
// --------------------------------------------------------
134-
// IsStandardSchemaV1Error
135-
// --------------------------------------------------------
136-
function IsStandardSchemaV1Error(error: TValidationErrorBase): error is TStandardSchemaV1Error {
137-
return Guard.IsEqual(error.keyword, '~standard')
138-
}
139-
// --------------------------------------------------------
140-
// IssuesFromLocalizedError
141-
// --------------------------------------------------------
142-
function IssuesFromStandardSchemaV1Error(error: TStandardSchemaV1Error): StandardSchemaV1.Issue[] {
143-
const leading = PathSegments(error.instancePath)
144-
const issues = Guard.IsArray(error.params.issues) ? error.params.issues : []
145-
return issues.map(issue => {
146-
const message = Guard.IsString(issue.message) ? issue.message : 'unknown'
147-
const path = Guard.IsArray(issue.path) ? [...leading, ...issue.path] : leading
148-
return { message, path }
149-
})
150-
}
151-
function IssuesFromRegularError(error: TLocalizedValidationError): StandardSchemaV1.Issue[] {
152-
const path = PathSegments(error.instancePath)
153-
return [{ path, message: error.message }]
154-
}
155-
function IssuesFromLocalizedError(error: TLocalizedValidationError): StandardSchemaV1.Issue[] {
156-
return IsStandardSchemaV1Error(error)
157-
? IssuesFromStandardSchemaV1Error(error)
158-
: IssuesFromRegularError(error)
159-
}
160-
// --------------------------------------------------------
161-
// IssuesFromUnknown
162-
// --------------------------------------------------------
163-
function IssuesFromUnknown(error: object): StandardSchemaV1.Issue[] {
164-
const path = Guard.HasPropertyKey(error, 'path') && Guard.IsArray(error.path) && error.path.every(segment => Guard.IsString(segment)) ? error.path : []
165-
const message = Guard.HasPropertyKey(error, 'message') && Guard.IsString(error.message) ? error.message : 'unknown'
166-
return [{ path, message } as StandardSchemaV1.Issue]
167-
}
168-
// --------------------------------------------------------
169-
// CreateIssues
170-
// --------------------------------------------------------
171-
function CreateIssues(error: object): StandardSchemaV1.Issue[] {
172-
return IsLocalizedValidationError(error)
173-
? IssuesFromLocalizedError(error)
174-
: IssuesFromUnknown(error)
100+
// ------------------------------------------------------------------
101+
// BaseValidator
102+
// ------------------------------------------------------------------
103+
class BaseValidator<Value extends unknown = unknown> implements StandardSchemaV1.Props<Value> {
104+
public readonly vendor = 'typebox'
105+
public readonly version = 1
106+
constructor(
107+
private readonly check: (value: unknown) => value is Value,
108+
private readonly errors: (value: unknown) => object[]
109+
) { }
110+
public readonly validate = (value: unknown): StandardSchemaV1.Result<Value> => {
111+
return this.check(value)
112+
? this.Success(value)
113+
: this.Failure(this.errors(value))
114+
}
115+
private Success(value: Value): StandardSchemaV1.SuccessResult<Value> {
116+
return { value }
117+
}
118+
private Failure(issues: object[]): StandardSchemaV1.FailureResult {
119+
return { issues } as never
120+
}
175121
}
176122
// ------------------------------------------------------------------
177-
// Standard Schema Interface
123+
// The Standard? Schema?? V1 Interface
178124
// ------------------------------------------------------------------
179125
interface StandardSchemaV1<Input = unknown, Output = Input> {
180126
readonly '~standard': StandardSchemaV1.Props<Input, Output>

0 commit comments

Comments
 (0)