Skip to content

Commit

Permalink
Use 'expected' and 'actual' props in ctx.reject and use 'Type' over '…
Browse files Browse the repository at this point in the history
…Type<unknown>'
  • Loading branch information
TizzySaurus committed Oct 26, 2024
1 parent fc67ff7 commit aa81782
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 38 deletions.
9 changes: 5 additions & 4 deletions ark/jsonschema/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,15 @@ const arrayItemsAreUnique = (

const arrayContainsItemMatchingSchema = (
array: readonly unknown[],
schema: Type<unknown>,
schema: Type,
ctx: TraversalContext
) =>
array.some(item => schema.allows(item)) === true ?
true
: ctx.mustBe(
"an array containing at least one item matching 'contains' schema"
)
: ctx.reject({
expected: `an array containing at least one item matching 'contains' schema of ${schema.description}`,
actual: printable(array)
})

export const validateJsonSchemaArray: Type<
(In: JsonSchema.ArraySchema) => Out<Type<unknown[], {}>>,
Expand Down
29 changes: 16 additions & 13 deletions ark/jsonschema/composition.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { printable } from "@ark/util"
import { type, type Type } from "arktype"
import { parseJsonSchema } from "./json.ts"
import type { JsonSchema } from "./scope.ts"

const validateAllOfJsonSchemas = (
jsonSchemas: JsonSchema.Schema[]
): Type<unknown> =>
const validateAllOfJsonSchemas = (jsonSchemas: JsonSchema.Schema[]): Type =>
jsonSchemas
.map(jsonSchema => parseJsonSchema(jsonSchema))
.reduce((acc, validator) => acc.and(validator))

const validateAnyOfJsonSchemas = (
jsonSchemas: JsonSchema.Schema[]
): Type<unknown> =>
const validateAnyOfJsonSchemas = (jsonSchemas: JsonSchema.Schema[]): Type =>
jsonSchemas
.map(jsonSchema => parseJsonSchema(jsonSchema))
.reduce((acc, validator) => acc.or(validator))

const validateNotJsonSchema = (jsonSchema: JsonSchema.Schema) => {
const inner = parseJsonSchema(jsonSchema)
return type("unknown").narrow((data, ctx) =>
inner.allows(data) ? ctx.mustBe(`not ${inner.description}`) : true
) as Type<unknown>
inner.allows(data) ?
ctx.reject({
expected: `a value that's not ${inner.description}`,
actual: printable(data)
})
: true
) as Type
}

const validateOneOfJsonSchemas = (jsonSchemas: JsonSchema.Schema[]) => {
Expand All @@ -41,15 +43,16 @@ const validateOneOfJsonSchemas = (jsonSchemas: JsonSchema.Schema[]) => {
matchedValidator = validator
continue
}
return ctx.mustBe(
`exactly one of:\n${oneOfValidatorsDescriptions.join("\n")}`
)
return ctx.reject({
expected: `exactly one of:\n${oneOfValidatorsDescriptions.join("\n")}`,
actual: printable(data)
})
}
}
return matchedValidator !== undefined
}) as Type<unknown>
}) as Type
)
// TODO: Theoretically this shouldn't be necessary due to above `ctx.mustBe` in narrow???
// TODO: Theoretically this shouldn't be necessary due to above `ctx.rejects` in narrow???
.describe(`one of:\n${oneOfValidatorsDescriptions.join("\n")}\n`)
)
}
Expand Down
2 changes: 1 addition & 1 deletion ark/jsonschema/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,5 @@ export const innerParseJsonSchema: Type<
return preTypeValidator
})

export const parseJsonSchema = (jsonSchema: JsonSchema.Schema): Type<unknown> =>
export const parseJsonSchema = (jsonSchema: JsonSchema.Schema): Type =>
innerParseJsonSchema.assert(jsonSchema) as never
51 changes: 31 additions & 20 deletions ark/jsonschema/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@ const parseMinMaxProperties = (

if ((jsonSchema.required?.length ?? 0) > maxProperties) {
ctx.reject({
message: `The specified JSON Schema requires at least ${jsonSchema.required?.length} properties, which exceeds the specified maxProperties of ${jsonSchema.maxProperties}.`
expected: `an object JSON Schema with at most ${jsonSchema.maxProperties} required properties`,
actual: `an object JSON Schema with ${jsonSchema.required!.length} required properties`
})
}
predicates.push((data: object, ctx) => {
const keys = Object.keys(data)
return keys.length <= maxProperties ?
true
: ctx.reject({
expected: `at most ${maxProperties} propert${maxProperties === 1 ? "y" : "ies"}`,
actual: keys.length.toString()
expected: `an object with at most ${maxProperties} propert${maxProperties === 1 ? "y" : "ies"}`,
actual: `an object with ${keys.length.toString()} propert${maxProperties === 1 ? "y" : "ies"}`
})
})
}
Expand All @@ -41,8 +42,8 @@ const parseMinMaxProperties = (
return keys.length >= minProperties ?
true
: ctx.reject({
expected: `at least ${minProperties} propert${minProperties === 1 ? "y" : "ies"}`,
actual: keys.length.toString()
expected: `an object with at least ${minProperties} propert${minProperties === 1 ? "y" : "ies"}`,
actual: `an object with ${keys.length.toString()} propert${minProperties === 1 ? "y" : "ies"}`
})
})
}
Expand All @@ -69,15 +70,17 @@ const parsePatternProperties = (

if (!parsedPropertySchema.overlaps(parsedPatternPropertySchema)) {
ctx.reject({
message: `property ${property} must have a schema that overlaps with the patternProperty ${pattern}`
path: [property],
expected: `a JSON Schema that overlaps with the schema for patternProperty ${pattern} (${parsedPatternPropertySchema.description})`,
actual: parsedPropertySchema.description
})
}
}
)
})

// NB: We don't validate compatability of schemas for overlapping patternProperties
// since getting the intersection of regexes is inherenetly difficult.
// since getting the intersection of regexes is inherently non-trivial.
return (data: object, ctx: TraversalContext) => {
const errors: false[] = []

Expand All @@ -86,8 +89,9 @@ const parsePatternProperties = (
if (pattern.test(dataKey) && !parsedJsonSchema.allows(dataValue)) {
errors.push(
ctx.reject({
actual: dataValue,
expected: `${parsedJsonSchema.description} as property ${dataKey} matches patternProperty ${pattern}`
path: [dataKey],
expected: `${parsedJsonSchema.description}, as property ${dataKey} matches patternProperty ${pattern}`,
actual: printable(dataValue)
})
)
}
Expand All @@ -111,8 +115,8 @@ const parsePropertyNames = (
) {
ctx.reject({
path: ["propertyNames"],
actual: `a schema for validating a ${propertyNamesValidator.json.domain as string}`,
expected: "a schema for validating a string"
expected: "a schema for validating a string",
actual: `a schema for validating a ${printable(propertyNamesValidator.json.domain)}`
})
}

Expand All @@ -123,7 +127,9 @@ const parsePropertyNames = (
if (!propertyNamesValidator.allows(key)) {
errors.push(
ctx.reject({
message: `property ${key} doesn't adhere to the propertyNames schema of ${propertyNamesValidator.description}`
path: [key],
expected: `a key adhering to the propertyNames schema of ${propertyNamesValidator.description}`,
actual: key
})
)
}
Expand All @@ -142,18 +148,19 @@ const parseRequiredAndOptionalKeys = (
if ("required" in jsonSchema) {
if (jsonSchema.required.length !== new Set(jsonSchema.required).size) {
ctx.reject({
path: ["required"],
expected: "an array of unique strings",
path: ["required"]
actual: printable(jsonSchema.required)
})
}

for (const key of jsonSchema.required) {
if (key in jsonSchema.properties) requiredKeys.push(key)
else {
ctx.reject({
actual: key,
expected: "a key in the 'properties' object",
path: ["required"]
path: ["required"],
expected: `a key from the 'properties' object (one of ${printable(Object.keys(jsonSchema.properties))})`,
actual: key
})
}
}
Expand All @@ -165,9 +172,9 @@ const parseRequiredAndOptionalKeys = (
}
} else if ("required" in jsonSchema) {
ctx.reject({
expected: "a valid object JSON Schema",
actual:
"an object JSON Schema with 'required' array but no 'properties' object",
expected: "a valid object JSON Schema"
"an object JSON Schema with 'required' array but no 'properties' object"
})
}

Expand Down Expand Up @@ -207,7 +214,9 @@ const parseAdditionalProperties = (jsonSchema: JsonSchema.ObjectSchema) => {
if (additionalPropertiesSchema === false) {
errors.push(
ctx.reject({
message: `property ${key} is an additional property, which the provided schema does not allow`
expected:
"an object with no additional keys, since provided additionalProperties JSON Schema doesn't allow it",
actual: `an additional key (${key})`
})
)
return
Expand All @@ -221,7 +230,9 @@ const parseAdditionalProperties = (jsonSchema: JsonSchema.ObjectSchema) => {
if (!additionalPropertyValidator.allows(value)) {
errors.push(
ctx.reject({
problem: `property ${key} is an additional property so must adhere to additional property schema of ${additionalPropertyValidator.description} (was ${printable(value)})`
path: [key],
expected: `${additionalPropertyValidator.description}, since ${key} is an additional property.`,
actual: printable(value)
})
)
}
Expand Down

0 comments on commit aa81782

Please sign in to comment.