Skip to content

Commit

Permalink
Revision 0.32.13 (#744)
Browse files Browse the repository at this point in the history
* Add String Length Contraints to RegExp

* Version

* Guard Test
  • Loading branch information
sinclairzx81 authored Jan 25, 2024
1 parent 22ee96a commit 52c71ed
Show file tree
Hide file tree
Showing 15 changed files with 113 additions and 12 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.32.12",
"version": "0.32.13",
"description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ export namespace TypeCompiler {
function* FromRegExp(schema: TRegExp, references: TSchema[], value: string): IterableIterator<string> {
const variable = CreateVariable(`${new RegExp(schema.source, schema.flags)};`)
yield `(typeof ${value} === 'string')`
if (IsNumber(schema.maxLength)) yield `${value}.length <= ${schema.maxLength}`
if (IsNumber(schema.minLength)) yield `${value}.length >= ${schema.minLength}`
yield `${variable}.test(${value})`
}
function* FromString(schema: TString, references: TSchema[], value: string): IterableIterator<string> {
Expand Down
7 changes: 7 additions & 0 deletions src/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,13 @@ function* FromRef(schema: TRef, references: TSchema[], path: string, value: any)
yield* Visit(Deref(schema, references), references, path, value)
}
function* FromRegExp(schema: TRegExp, references: TSchema[], path: string, value: any): IterableIterator<ValueError> {
if (!IsString(value)) return yield Create(ValueErrorType.String, schema, path, value)
if (IsDefined<number>(schema.minLength) && !(value.length >= schema.minLength)) {
yield Create(ValueErrorType.StringMinLength, schema, path, value)
}
if (IsDefined<number>(schema.maxLength) && !(value.length <= schema.maxLength)) {
yield Create(ValueErrorType.StringMaxLength, schema, path, value)
}
const regex = new RegExp(schema.source, schema.flags)
if (!regex.test(value)) {
return yield Create(ValueErrorType.RegExp, schema, path, value)
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export { ReadonlyOptional, type TReadonlyOptional } from './type/readonly-option
export { Record, type TRecord, type TRecordOrObject } from './type/record/index'
export { Recursive, type TRecursive, type TThis } from './type/recursive/index'
export { Ref, type TRef } from './type/ref/index'
export { RegExp, type TRegExp } from './type/regexp/index'
export { RegExp, type TRegExp, type RegExpOptions } from './type/regexp/index'
export { Required, type TRequired, type TRequiredFromMappedResult } from './type/required/index'
export { Rest, type TRest } from './type/rest/index'
export { ReturnType, type TReturnType } from './type/return-type/index'
Expand Down
4 changes: 3 additions & 1 deletion src/type/guard/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,9 @@ export function IsRegExp(value: unknown): value is TRegExp {
IsKindOf(value, 'RegExp') &&
IsOptionalString(value.$id) &&
ValueGuard.IsString(value.source) &&
ValueGuard.IsString(value.flags)
ValueGuard.IsString(value.flags) &&
IsOptionalNumber(value.maxLength) &&
IsOptionalNumber(value.minLength)
)
}
/** Returns true if the given value is TString */
Expand Down
12 changes: 9 additions & 3 deletions src/type/regexp/regexp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ import type { TSchema } from '../schema/index'
import { IsString } from '../guard/value'
import { Kind } from '../symbols/index'

export interface RegExpOptions extends SchemaOptions {
/** The maximum length of the string */
maxLength?: number
/** The minimum length of the string */
minLength?: number
}
export interface TRegExp extends TSchema {
[Kind]: 'RegExp'
static: `${string}`
Expand All @@ -40,11 +46,11 @@ export interface TRegExp extends TSchema {
}

/** `[JavaScript]` Creates a RegExp type */
export function RegExp(pattern: string, options?: SchemaOptions): TRegExp
export function RegExp(pattern: string, options?: RegExpOptions): TRegExp
/** `[JavaScript]` Creates a RegExp type */
export function RegExp(regex: RegExp, options?: SchemaOptions): TRegExp
export function RegExp(regex: RegExp, options?: RegExpOptions): TRegExp
/** `[JavaScript]` Creates a RegExp type */
export function RegExp(unresolved: RegExp | string, options: SchemaOptions = {}) {
export function RegExp(unresolved: RegExp | string, options: RegExpOptions = {}) {
const expr = IsString(unresolved) ? new globalThis.RegExp(unresolved) : unresolved
return { ...options, [Kind]: 'RegExp', type: 'RegExp', source: expr.source, flags: expr.flags } as never
}
8 changes: 4 additions & 4 deletions src/type/type/javascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { InstanceType, type TInstanceType } from '../instance-type/index'
import { Iterator, type TIterator } from '../iterator/index'
import { Parameters, type TParameters } from '../parameters/index'
import { Promise, type TPromise } from '../promise/index'
import { RegExp, type TRegExp } from '../regexp/index'
import { RegExp, type TRegExp, RegExpOptions } from '../regexp/index'
import { ReturnType, type TReturnType } from '../return-type/index'
import { type TSchema, type SchemaOptions } from '../schema/index'
import { Symbol, type TSymbol } from '../symbol/index'
Expand Down Expand Up @@ -93,11 +93,11 @@ export class JavaScriptTypeBuilder extends JsonTypeBuilder {
return Promise(item, options)
}
/** `[JavaScript]` Creates a RegExp type */
public RegExp(pattern: string, options?: SchemaOptions): TRegExp
public RegExp(pattern: string, options?: RegExpOptions): TRegExp
/** `[JavaScript]` Creates a RegExp type */
public RegExp(regex: RegExp, options?: SchemaOptions): TRegExp
public RegExp(regex: RegExp, options?: RegExpOptions): TRegExp
/** `[JavaScript]` Creates a RegExp type */
public RegExp(unresolved: string | RegExp, options: SchemaOptions = {}) {
public RegExp(unresolved: string | RegExp, options: RegExpOptions = {}) {
return RegExp(unresolved as any, options)
}
/** `[JavaScript]` Extracts the ReturnType from the given Function type */
Expand Down
6 changes: 6 additions & 0 deletions src/value/check/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@ function FromRef(schema: TRef, references: TSchema[], value: any): boolean {
}
function FromRegExp(schema: TRegExp, references: TSchema[], value: any): boolean {
const regex = new RegExp(schema.source, schema.flags)
if (IsDefined<number>(schema.minLength)) {
if (!(value.length >= schema.minLength)) return false
}
if (IsDefined<number>(schema.maxLength)) {
if (!(value.length <= schema.maxLength)) return false
}
return regex.test(value)
}
function FromString(schema: TString, references: TSchema[], value: any): boolean {
Expand Down
14 changes: 14 additions & 0 deletions test/runtime/compiler/regexp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,18 @@ describe('compiler/RegExp', () => {
const T = Type.RegExp(/<a?:.+?:\d{18}>|\p{Extended_Pictographic}/gu)
Ok(T, '♥️♦️♠️♣️')
})
it('Should validate with minLength constraint', () => {
const T = Type.RegExp(/(.*)/, {
minLength: 3,
})
Ok(T, 'xxx')
Fail(T, 'xx')
})
it('Should validate with maxLength constraint', () => {
const T = Type.RegExp(/(.*)/, {
maxLength: 3,
})
Ok(T, 'xxx')
Fail(T, 'xxxx')
})
})
2 changes: 2 additions & 0 deletions test/runtime/errors/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import './object-required-property'
import './object'
import './promise'
import './record-pointer-property'
import './regexp-max-length'
import './regexp-min-length'
import './regexp'
import './string-format-unknown'
import './string-format'
Expand Down
19 changes: 19 additions & 0 deletions test/runtime/errors/types/regexp-max-length.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Type, FormatRegistry } from '@sinclair/typebox'
import { ValueErrorType } from '@sinclair/typebox/errors'
import { Resolve } from './resolve'
import { Assert } from '../../assert'

describe('errors/type/RegExpMaxLength', () => {
const T = Type.RegExp(/(.*)/, {
maxLength: 4,
})
it('Should pass 0', () => {
const R = Resolve(T, 'xxxx')
Assert.IsEqual(R.length, 0)
})
it('Should pass 1', () => {
const R = Resolve(T, 'xxxxx')
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].type, ValueErrorType.StringMaxLength)
})
})
19 changes: 19 additions & 0 deletions test/runtime/errors/types/regexp-min-length.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Type } from '@sinclair/typebox'
import { ValueErrorType } from '@sinclair/typebox/errors'
import { Resolve } from './resolve'
import { Assert } from '../../assert'

describe('errors/type/RegExpMinLength', () => {
const T = Type.RegExp(/(.*)/, {
minLength: 4,
})
it('Should pass 0', () => {
const R = Resolve(T, 'xxxx')
Assert.IsEqual(R.length, 0)
})
it('Should pass 1', () => {
const R = Resolve(T, 'xxx')
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].type, ValueErrorType.StringMinLength)
})
})
10 changes: 10 additions & 0 deletions test/runtime/type/guard/regexp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,14 @@ describe('type/guard/TRegExp', () => {
const T = Type.RegExp('foo', { $id: 1 })
Assert.IsFalse(TypeGuard.IsRegExp(T))
})
it('Should guard for RegExp constraint 1', () => {
// @ts-ignore
const T = Type.RegExp('foo', { maxLength: '1' })
Assert.IsFalse(TypeGuard.IsRegExp(T))
})
it('Should guard for RegExp constraint 2', () => {
// @ts-ignore
const T = Type.RegExp('foo', { minLength: '1' })
Assert.IsFalse(TypeGuard.IsRegExp(T))
})
})
14 changes: 14 additions & 0 deletions test/runtime/value/check/regexp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,18 @@ describe('value/check/RegExp', () => {
const result = Value.Check(T, value)
Assert.IsEqual(result, false)
})
it('Should validate with minLength constraint', () => {
const T = Type.RegExp(/(.*)/, {
minLength: 3,
})
Assert.IsTrue(Value.Check(T, 'xxx'))
Assert.IsFalse(Value.Check(T, 'xx'))
})
it('Should validate with maxLength constraint', () => {
const T = Type.RegExp(/(.*)/, {
maxLength: 3,
})
Assert.IsTrue(Value.Check(T, 'xxx'))
Assert.IsFalse(Value.Check(T, 'xxxx'))
})
})

0 comments on commit 52c71ed

Please sign in to comment.