Revision 0.31.19 (#644)

* Encode Error Path for RFC9601 Escape Sequences

* Record Tests + Version
This commit is contained in:
sinclairzx81
2023-10-26 03:26:27 +09:00
committed by GitHub
parent 0252b4a696
commit b0b6689000
6 changed files with 229 additions and 12 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@sinclair/typebox",
"version": "0.31.18",
"version": "0.31.19",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@sinclair/typebox",
"version": "0.31.18",
"version": "0.31.19",
"license": "MIT",
"devDependencies": {
"@sinclair/hammer": "^0.18.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.31.18",
"version": "0.31.19",
"description": "JSONSchema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",

View File

@@ -119,6 +119,12 @@ export class ValueErrorsUnknownTypeError extends Types.TypeBoxError {
}
}
// --------------------------------------------------------------------------
// EscapeKey
// --------------------------------------------------------------------------
export function EscapeKey(key: string): string {
return key.replace(/~/g, '~0').replace(/\//g, '~1') // RFC6901 Path
}
// --------------------------------------------------------------------------
// Guards
// --------------------------------------------------------------------------
function IsDefined<T>(value: unknown): value is T {
@@ -319,31 +325,31 @@ function* TObject(schema: Types.TObject, references: Types.TSchema[], path: stri
const unknownKeys = Object.getOwnPropertyNames(value)
for (const requiredKey of requiredKeys) {
if (unknownKeys.includes(requiredKey)) continue
yield Create(ValueErrorType.ObjectRequiredProperty, schema.properties[requiredKey], `${path}/${requiredKey}`, undefined)
yield Create(ValueErrorType.ObjectRequiredProperty, schema.properties[requiredKey], `${path}/${EscapeKey(requiredKey)}`, undefined)
}
if (schema.additionalProperties === false) {
for (const valueKey of unknownKeys) {
if (!knownKeys.includes(valueKey)) {
yield Create(ValueErrorType.ObjectAdditionalProperties, schema, `${path}/${valueKey}`, value[valueKey])
yield Create(ValueErrorType.ObjectAdditionalProperties, schema, `${path}/${EscapeKey(valueKey)}`, value[valueKey])
}
}
}
if (typeof schema.additionalProperties === 'object') {
for (const valueKey of unknownKeys) {
if (knownKeys.includes(valueKey)) continue
yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${valueKey}`, value[valueKey])
yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${EscapeKey(valueKey)}`, value[valueKey])
}
}
for (const knownKey of knownKeys) {
const property = schema.properties[knownKey]
if (schema.required && schema.required.includes(knownKey)) {
yield* Visit(property, references, `${path}/${knownKey}`, value[knownKey])
yield* Visit(property, references, `${path}/${EscapeKey(knownKey)}`, value[knownKey])
if (Types.ExtendsUndefined.Check(schema) && !(knownKey in value)) {
yield Create(ValueErrorType.ObjectRequiredProperty, property, `${path}/${knownKey}`, undefined)
yield Create(ValueErrorType.ObjectRequiredProperty, property, `${path}/${EscapeKey(knownKey)}`, undefined)
}
} else {
if (TypeSystemPolicy.IsExactOptionalProperty(value, knownKey)) {
yield* Visit(property, references, `${path}/${knownKey}`, value[knownKey])
yield* Visit(property, references, `${path}/${EscapeKey(knownKey)}`, value[knownKey])
}
}
}
@@ -362,17 +368,17 @@ function* TRecord(schema: Types.TRecord, references: Types.TSchema[], path: stri
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
const regex = new RegExp(patternKey)
for (const [propertyKey, propertyValue] of Object.entries(value)) {
if (regex.test(propertyKey)) yield* Visit(patternSchema, references, `${path}/${propertyKey}`, propertyValue)
if (regex.test(propertyKey)) yield* Visit(patternSchema, references, `${path}/${EscapeKey(propertyKey)}`, propertyValue)
}
if (typeof schema.additionalProperties === 'object') {
for (const [propertyKey, propertyValue] of Object.entries(value)) {
if (!regex.test(propertyKey)) yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${propertyKey}`, propertyValue)
if (!regex.test(propertyKey)) yield* Visit(schema.additionalProperties as Types.TSchema, references, `${path}/${EscapeKey(propertyKey)}`, propertyValue)
}
}
if (schema.additionalProperties === false) {
for (const [propertyKey, propertyValue] of Object.entries(value)) {
if (regex.test(propertyKey)) continue
return yield Create(ValueErrorType.ObjectAdditionalProperties, schema, `${path}/${propertyKey}`, propertyValue)
return yield Create(ValueErrorType.ObjectAdditionalProperties, schema, `${path}/${EscapeKey(propertyKey)}`, propertyValue)
}
}
}

View File

@@ -43,9 +43,11 @@ import './number-multiple-of'
import './object-additional-properties'
import './object-max-properties'
import './object-min-properties'
import './object-pointer-property'
import './object-required-property'
import './object'
import './promise'
import './record-pointer-property'
import './string-format-unknown'
import './string-format'
import './string-max-length'

View File

@@ -0,0 +1,139 @@
import { Type } from '@sinclair/typebox'
import { Resolve } from './resolve'
import { Assert } from '../../assert'
describe('errors/type/ObjectPointerProperty', () => {
// ----------------------------------------------------------------
// Known
// ----------------------------------------------------------------
it('Should produce known pointer property path 1', () => {
const T = Type.Object({ 'a/b': Type.String() })
const R = Resolve(T, { 'a/b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~1b')
})
it('Should produce known pointer property path 2', () => {
const T = Type.Object({ 'a~b': Type.String() })
const R = Resolve(T, { 'a~b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b')
})
it('Should produce known pointer property path 3', () => {
const T = Type.Object({ 'a/b~c': Type.String() })
const R = Resolve(T, { 'a/b~c': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~1b~0c')
})
it('Should produce known pointer property path 4', () => {
const T = Type.Object({ 'a~b/c': Type.String() })
const R = Resolve(T, { 'a~b/c': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b~1c')
})
it('Should produce known pointer property path 5', () => {
const T = Type.Object({ 'a~b/c/d': Type.String() })
const R = Resolve(T, { 'a~b/c/d': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b~1c~1d')
})
it('Should produce known pointer property path 6', () => {
const T = Type.Object({ 'a~b/c/d~e': Type.String() })
const R = Resolve(T, { 'a~b/c/d~e': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b~1c~1d~0e')
})
// ----------------------------------------------------------------
// Unknown Additional
// ----------------------------------------------------------------
it('Should produce unknown pointer property path 1', () => {
const T = Type.Object({}, { additionalProperties: false })
const R = Resolve(T, { 'a/b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~1b')
})
it('Should produce unknown pointer property path 2', () => {
const T = Type.Object({}, { additionalProperties: false })
const R = Resolve(T, { 'a~b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b')
})
// ----------------------------------------------------------------
// Unknown Constrained
// ----------------------------------------------------------------
it('Should produce unknown constrained pointer property path 1', () => {
const T = Type.Object({}, { additionalProperties: Type.String() })
const R = Resolve(T, { 'a/b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~1b')
})
it('Should produce unknown constrained pointer property path 2', () => {
const T = Type.Object({}, { additionalProperties: Type.String() })
const R = Resolve(T, { 'a~b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b')
})
// ----------------------------------------------------------------
// Nested
// ----------------------------------------------------------------
it('Should produce nested pointer 1', () => {
const T = Type.Object({
'x/y': Type.Object({
z: Type.Object({
w: Type.String(),
}),
}),
})
const R = Resolve(T, { 'x/y': { z: { w: 1 } } })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/x~1y/z/w')
})
it('Should produce nested pointer 2', () => {
const T = Type.Object({
x: Type.Object({
'y/z': Type.Object({
w: Type.String(),
}),
}),
})
const R = Resolve(T, { x: { 'y/z': { w: 1 } } })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/x/y~1z/w')
})
it('Should produce nested pointer 3', () => {
const T = Type.Object({
x: Type.Object({
y: Type.Object({
'z/w': Type.String(),
}),
}),
})
const R = Resolve(T, { x: { y: { 'z/w': 1 } } })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/x/y/z~1w')
})
// ----------------------------------------------------------------
// Nested Array
// ----------------------------------------------------------------
it('Should produce nested array pointer property path 1', () => {
const T = Type.Object({
'x/y': Type.Object({
z: Type.Array(Type.String()),
}),
})
const R = Resolve(T, { 'x/y': { z: ['a', 'b', 1] } })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/x~1y/z/2')
})
it('Should produce nested array pointer property path 2', () => {
const T = Type.Object({
x: Type.Array(
Type.Object({
'y/z': Type.String(),
}),
),
})
const R = Resolve(T, { x: [{ 'y/z': 'a' }, { 'y/z': 'b' }, { 'y/z': 1 }] })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/x/2/y~1z')
})
})

View File

@@ -0,0 +1,70 @@
import { Type } from '@sinclair/typebox'
import { Resolve } from './resolve'
import { Assert } from '../../assert'
describe('errors/type/RecordPointerProperty', () => {
// ----------------------------------------------------------------
// Known
// ----------------------------------------------------------------
it('Should produce known pointer property path 1', () => {
const T = Type.Record(Type.String(), Type.String())
const R = Resolve(T, { 'a/b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~1b')
})
it('Should produce known pointer property path 2', () => {
const T = Type.Record(Type.String(), Type.String())
const R = Resolve(T, { 'a~b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b')
})
// ----------------------------------------------------------------
// Unknown
// ----------------------------------------------------------------
it('Should produce unknown pointer property path 1', () => {
const T = Type.Record(Type.Number(), Type.String(), {
additionalProperties: false,
})
const R = Resolve(T, { 'a/b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~1b')
})
it('Should produce unknown pointer property path 1', () => {
const T = Type.Record(Type.Number(), Type.String(), {
additionalProperties: false,
})
const R = Resolve(T, { 'a~b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b')
})
// ----------------------------------------------------------------
// Unknown Constrained
// ----------------------------------------------------------------
it('Should produce unknown constrained pointer property path 1', () => {
const T = Type.Record(Type.Number(), Type.String(), {
additionalProperties: Type.String(),
})
const R = Resolve(T, { 'a/b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~1b')
})
it('Should produce unknown constrained pointer property path 1', () => {
const T = Type.Record(Type.Number(), Type.String(), {
additionalProperties: Type.String(),
})
const R = Resolve(T, { 'a~b': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/a~0b')
})
// ----------------------------------------------------------------
// PatternProperties
// ----------------------------------------------------------------
it('Should produce pattern pointer property path 1', () => {
const T = Type.Record(Type.TemplateLiteral('${string}/${string}/c'), Type.String(), {
additionalProperties: false,
})
const R = Resolve(T, { 'x/y/z': 1 })
Assert.IsEqual(R.length, 1)
Assert.IsEqual(R[0].path, '/x~1y~1z')
})
})