-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
96 changed files
with
1,001 additions
and
1,095 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,7 @@ | ||
export interface SchemaContext { | ||
/** Exact path of the current context. */ | ||
path: Array<string | number>; | ||
} | ||
|
||
/** Create an empty `SchemaContext` ready to use. */ | ||
export function createContext(): SchemaContext { | ||
return { path: [] }; | ||
export interface Context { | ||
/** | ||
* Skip the accumulation of issues during execution-time. | ||
* @default false | ||
*/ | ||
verbose: boolean; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,46 @@ | ||
import { SchemaContext } from "./context.ts"; | ||
|
||
/** Describes one single issue during the process of validation. */ | ||
export interface SchemaIssue extends SchemaContext { | ||
message?: string; | ||
issues?: SchemaIssue[]; | ||
interface IssueBase { | ||
reason: string; | ||
/** Stack of issues with more details. */ | ||
issues?: Issue[]; | ||
/** Current `index` or `key` where the issue was generated. */ | ||
position?: unknown; | ||
} | ||
|
||
/** Used to gather all the issues during the validation phase. */ | ||
export class SchemaError extends Error { | ||
readonly issues: SchemaIssue[]; | ||
|
||
constructor(issue: SchemaIssue) { | ||
super(issue.message); | ||
|
||
this.issues = [issue]; | ||
} | ||
|
||
/** | ||
* Flatten the stack of issues by returning the non-nested ones. | ||
* @returns Summarized array of issues. | ||
*/ | ||
flatten() { | ||
return flatten(this.issues); | ||
} | ||
|
||
/** | ||
* Flatten the stack of issues and returns the first one. | ||
* @returns First issue in the flatten stack. | ||
*/ | ||
first() { | ||
const [first] = this.flatten(); | ||
return first; | ||
} | ||
/** Received type is not the expected. */ | ||
export const ISSUE_TYPE_REASON = "TYPE"; | ||
|
||
/** Received type is not the expected. */ | ||
export interface IssueType extends IssueBase { | ||
reason: typeof ISSUE_TYPE_REASON; | ||
expected: unknown; | ||
received?: unknown; | ||
} | ||
|
||
/** | ||
* Creates a new `SchemaError` using the current `context` and the `descriptor`. | ||
* @returns Instance of `SchemaError`. | ||
*/ | ||
export function createError( | ||
context: SchemaContext, | ||
descriptor?: Omit<SchemaIssue, keyof SchemaContext>, | ||
) { | ||
return new SchemaError({ ...context, ...descriptor }); | ||
export const ISSUE_VALIDATION_REASON = "VALIDATION"; | ||
|
||
/** Received value does not fulfill the constraint. */ | ||
export interface IssueValidation extends IssueBase { | ||
reason: typeof ISSUE_VALIDATION_REASON; | ||
expected?: unknown; | ||
received?: unknown; | ||
} | ||
|
||
/** Flatten a stack of issues by returning the non-nested ones. */ | ||
function flatten(issues: SchemaIssue[]): SchemaIssue[] { | ||
return issues.flatMap((issue) => { | ||
const stack = issue.issues ?? []; | ||
/** Expect one value but no one was received. */ | ||
export const ISSUE_MISSING_REASON = "MISSING"; | ||
|
||
if (stack.length > 0) { | ||
return flatten(stack); | ||
} | ||
/** Expect one value but no one was received. */ | ||
export interface IssueMissing extends IssueBase { | ||
reason: typeof ISSUE_MISSING_REASON; | ||
} | ||
|
||
/** Expect no value but one was received. */ | ||
export const ISSUE_PRESENT_REASON = "PRESENT"; | ||
|
||
return issue; | ||
}); | ||
/** Expect no value but one was received. */ | ||
export interface IssuePresent extends IssueBase { | ||
reason: typeof ISSUE_PRESENT_REASON; | ||
} | ||
|
||
export type Issue = Readonly< | ||
IssueType | IssueValidation | IssueMissing | IssuePresent | ||
>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,14 @@ | ||
// Copyright (c) 2023 Andres Celis. MIT license. | ||
// Copyright (c) 2024 Andres Celis. MIT license. | ||
|
||
/** | ||
* An awesome, tiny and extensible runtime types validation library. | ||
* @module | ||
*/ | ||
|
||
export * from "./schema.ts"; | ||
export * from "./context.ts"; | ||
export * from "./errors.ts"; | ||
export * from "./types.ts"; | ||
export * from "./pipes/mod.ts"; | ||
export * from "./schemas/mod.ts"; | ||
export * from "./utils/mod.ts"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,11 @@ | ||
import { assertEquals, assertIsError } from "assert/mod.ts"; | ||
import { createContext } from "../context.ts"; | ||
import { assertObjectMatch } from "assert/mod.ts"; | ||
import { pipe, string } from "../schemas/mod.ts"; | ||
import { isEmail } from "./isEmail.ts"; | ||
import { safeParse } from "../utils/mod.ts"; | ||
|
||
const context = createContext(); | ||
|
||
Deno.test("should assert formatted emails", () => { | ||
const schema = pipe(string(), isEmail()); | ||
const schema = pipe(string(), isEmail()); | ||
|
||
Deno.test("assert formatted emails", () => { | ||
const correct = [ | ||
`[email protected]`, | ||
`[email protected]`, | ||
|
@@ -34,10 +32,12 @@ Deno.test("should assert formatted emails", () => { | |
]; | ||
|
||
for (const received of correct) { | ||
assertEquals(schema.check(received, context), received); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: true, value: received }); | ||
} | ||
|
||
for (const received of incorrect) { | ||
assertIsError(schema.check(received, context)); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: false }); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,10 @@ | ||
import { isMatch } from "./isMatch.ts"; | ||
|
||
const REGEX = /^[A-Z0-9._%+-]+@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i; | ||
const ERROR_MESSAGE = "Must be a valid email"; | ||
|
||
/** | ||
* Check the `value` as an email. | ||
*/ | ||
export function isEmail(message = ERROR_MESSAGE) { | ||
return isMatch(REGEX, message); | ||
export function isEmail() { | ||
return isMatch(REGEX); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,21 @@ | ||
import { assertEquals, assertIsError } from "assert/mod.ts"; | ||
import { createContext } from "../context.ts"; | ||
import { assertObjectMatch } from "assert/mod.ts"; | ||
import { number, pipe } from "../schemas/mod.ts"; | ||
import { isInteger } from "./isInteger.ts"; | ||
import { safeParse } from "../utils/mod.ts"; | ||
|
||
const context = createContext(); | ||
const schema = pipe(number(), isInteger()); | ||
|
||
Deno.test("should assert integer numbers", () => { | ||
Deno.test("assert integer numbers", () => { | ||
const correct = [1, -1, 100_000]; | ||
const incorrect = [1.23, 0.12, 0.1 + 0.2]; | ||
|
||
for (const received of correct) { | ||
assertEquals(schema.check(received, context), received); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: true, value: received }); | ||
} | ||
|
||
for (const received of incorrect) { | ||
assertIsError(schema.check(received, context)); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: false }); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,18 @@ | ||
import { SchemaContext } from "../context.ts"; | ||
import { createError } from "../errors.ts"; | ||
|
||
const ERROR_MESSAGE = "Must be a integer number"; | ||
import { failure } from "../schema.ts"; | ||
|
||
/** | ||
* Check the `value` as a integer number using the `Number.isInteger` function. | ||
*/ | ||
export function isInteger(message = ERROR_MESSAGE) { | ||
return function (value: number, context: SchemaContext) { | ||
export function isInteger() { | ||
return function (value: number) { | ||
if (Number.isInteger(value)) { | ||
return value; | ||
} | ||
|
||
return createError(context, { message }); | ||
return failure({ | ||
reason: "VALIDATION", | ||
expected: "integer", | ||
received: value, | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,21 @@ | ||
import { assertEquals, assertIsError } from "assert/mod.ts"; | ||
import { createContext } from "../context.ts"; | ||
import { assertObjectMatch } from "assert/mod.ts"; | ||
import { pipe, string } from "../schemas/mod.ts"; | ||
import { isMatch } from "./isMatch.ts"; | ||
import { safeParse } from "../utils/mod.ts"; | ||
|
||
const context = createContext(); | ||
const schema = pipe(string(), isMatch(/[A-Z]{3}-\d{1,}/i)); | ||
|
||
Deno.test("should pass accepted values on the regular-expression", () => { | ||
Deno.test("pass accepted values on the regular-expression", () => { | ||
const correct = ["ABC-123", "XYZ-456"]; | ||
const incorrect = ["ABCD", "AB-1234"]; | ||
|
||
for (const received of correct) { | ||
assertEquals(schema.check(received, context), received); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: true, value: received }); | ||
} | ||
|
||
for (const received of incorrect) { | ||
assertIsError(schema.check(received, context)); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: false }); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,18 @@ | ||
import { SchemaContext } from "../context.ts"; | ||
import { createError } from "../errors.ts"; | ||
import { failure } from "../schema.ts"; | ||
|
||
/** | ||
* Create a pipe that validates the `value` as the specified regular expression. | ||
*/ | ||
export function isMatch( | ||
regex: RegExp, | ||
message = `Must match with "${regex}" expression`, | ||
) { | ||
return function (value: string, context: SchemaContext) { | ||
export function isMatch(regex: RegExp) { | ||
return function (value: string) { | ||
if (regex.test(value)) { | ||
return value; | ||
} | ||
|
||
return createError(context, { message }); | ||
return failure({ | ||
reason: "VALIDATION", | ||
expected: regex, | ||
received: value, | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,21 @@ | ||
import { assertEquals, assertIsError } from "assert/mod.ts"; | ||
import { createContext } from "../context.ts"; | ||
import { assertObjectMatch } from "assert/mod.ts"; | ||
import { number, pipe } from "../schemas/mod.ts"; | ||
import { isNegative } from "./isNegative.ts"; | ||
import { safeParse } from "../utils/mod.ts"; | ||
|
||
const context = createContext(); | ||
const schema = pipe(number(), isNegative()); | ||
|
||
Deno.test("should assert negative numbers", () => { | ||
Deno.test("assert negative numbers", () => { | ||
const correct = [-1, -10, -100_000]; | ||
const incorrect = [1, 10, 100_000]; | ||
|
||
for (const received of correct) { | ||
assertEquals(schema.check(received, context), received); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: true, value: received }); | ||
} | ||
|
||
for (const received of incorrect) { | ||
assertIsError(schema.check(received, context)); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: false }); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,18 @@ | ||
import { SchemaContext } from "../context.ts"; | ||
import { createError } from "../errors.ts"; | ||
|
||
const ERROR_MESSAGE = "Must be a negative number"; | ||
import { failure } from "../schema.ts"; | ||
|
||
/** | ||
* Check the `value` as a negative number. | ||
*/ | ||
export function isNegative(message = ERROR_MESSAGE) { | ||
return function (value: number, context: SchemaContext) { | ||
export function isNegative() { | ||
return function (value: number) { | ||
if (value < 0) { | ||
return value; | ||
} | ||
|
||
return createError(context, { message }); | ||
return failure({ | ||
reason: "VALIDATION", | ||
expected: "negative", | ||
received: value, | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,21 @@ | ||
import { assertEquals, assertIsError } from "assert/mod.ts"; | ||
import { createContext } from "../context.ts"; | ||
import { assertObjectMatch } from "assert/mod.ts"; | ||
import { number, pipe } from "../schemas/mod.ts"; | ||
import { isPositive } from "./isPositive.ts"; | ||
import { safeParse } from "../utils/mod.ts"; | ||
|
||
const context = createContext(); | ||
const schema = pipe(number(), isPositive()); | ||
|
||
Deno.test("should assert negative numbers", () => { | ||
Deno.test("assert negative numbers", () => { | ||
const correct = [1, 10, 100_000]; | ||
const incorrect = [-1, -10, -100_000]; | ||
|
||
for (const received of correct) { | ||
assertEquals(schema.check(received, context), received); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: true, value: received }); | ||
} | ||
|
||
for (const received of incorrect) { | ||
assertIsError(schema.check(received, context)); | ||
const commit = safeParse(received, schema); | ||
assertObjectMatch(commit, { success: false }); | ||
} | ||
}); |
Oops, something went wrong.