Skip to content

Commit

Permalink
fix: #210 renderJSONSchemaEnum not working enums inside an array
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

The TypeScript definitions of `createAjvValidator` and  `renderJSONSchemaEnum` are
changed: passing `JSONSchema` and `JSONSchemaDefinitions` instead of `JSONValue`.
  • Loading branch information
josdejong committed Jan 13, 2023
1 parent fe21ffb commit 887bf23
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 75 deletions.
12 changes: 6 additions & 6 deletions src/lib/plugins/validator/createAjvValidator.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { Options, Schema } from 'ajv'
import type Ajv from 'ajv'
import type { Options, Schema } from 'ajv'
import AjvDist from 'ajv'
import type { JSONValue } from 'immutable-json-patch'
import { parsePath } from 'immutable-json-patch'
import type { ValidationError, Validator } from '../../types'
import { ValidationSeverity } from '../../types.js'
import type { JSONSchema, JSONSchemaDefinitions, ValidationError, Validator } from '$lib/types'
import { ValidationSeverity } from '$lib/types.js'

export interface AjvValidatorOptions {
schema: JSONValue
schemaDefinitions?: JSONValue
schema: JSONSchema
schemaDefinitions?: JSONSchemaDefinitions
ajvOptions?: Options
onCreateAjv?: (ajv: Ajv) => Ajv | void
}
Expand Down Expand Up @@ -69,7 +69,7 @@ function createAjvInstance(options: AjvValidatorOptions): Ajv {

if (schemaDefinitions) {
Object.keys(schemaDefinitions).forEach((ref) => {
ajv.addSchema(schemaDefinitions[ref], ref)
ajv.addSchema(schemaDefinitions[ref] as Schema, ref)
})
}

Expand Down
16 changes: 10 additions & 6 deletions src/lib/plugins/value/renderJSONSchemaEnum.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import EnumValue from './components/EnumValue.svelte'
import { getJSONSchemaOptions } from '../../utils/jsonSchemaUtils.js'
import type { RenderValueComponentDescription, RenderValueProps } from '../../types'
import type { JSONValue } from 'immutable-json-patch'
import { getJSONSchemaOptions } from '$lib/utils/jsonSchemaUtils.js'
import type {
JSONSchema,
JSONSchemaDefinitions,
RenderValueComponentDescription,
RenderValueProps
} from '$lib/types'
import type { SvelteComponentTyped } from 'svelte'

/**
Expand All @@ -11,8 +15,8 @@ import type { SvelteComponentTyped } from 'svelte'
*/
export function renderJSONSchemaEnum(
props: RenderValueProps,
schema: JSONValue,
schemaDefinitions: JSONValue
schema: JSONSchema,
schemaDefinitions?: JSONSchemaDefinitions
): RenderValueComponentDescription[] {
const enumValues = getJSONSchemaOptions(schema, schemaDefinitions, props.path)

Expand All @@ -28,7 +32,7 @@ export function renderJSONSchemaEnum(
// else it would look as if the first option is the current value
const optionsWithValue = enumValues.includes(props.value)
? options
: [{ value, text: value }].concat(options)
: [{ value: value as unknown, text: value as unknown }].concat(options)

return [
{
Expand Down
6 changes: 6 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -598,3 +598,9 @@ export interface SortedColumn {
path: JSONPath
sortDirection: SortDirection
}

// TODO: work out the JSONSchema type in detail.
// Ideally, we use use Schema from Ajv, but this interface isn't worked out either
export type JSONSchema = Record<string, unknown>
export type JSONSchemaDefinitions = Record<string, JSONSchema>
export type JSONSchemaEnum = Array<unknown>
14 changes: 7 additions & 7 deletions src/lib/utils/jsonSchemaUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('jsonSchemaUtils', () => {
}
}

const options = getJSONSchemaOptions(schema, null, ['job', 1, 'company'])
const options = getJSONSchemaOptions(schema, null, ['job', '1', 'company'])
assert.deepStrictEqual(options, ['test1', 'test2'])
})
})
Expand Down Expand Up @@ -61,10 +61,10 @@ describe('jsonSchemaUtils', () => {

assert.strictEqual(findSchema(schema, {}, ['job']), schema.properties.job)

assert.strictEqual(findSchema(schema, {}, ['job', 0]), schema.properties.job.items)
assert.strictEqual(findSchema(schema, {}, ['job', '0']), schema.properties.job.items)

assert.strictEqual(
findSchema(schema, {}, ['job', 0, 'company']),
findSchema(schema, {}, ['job', '0', 'company']),
schema.properties.job.items.properties.company
)
})
Expand Down Expand Up @@ -160,7 +160,7 @@ describe('jsonSchemaUtils', () => {
}
}
}
const path = ['aProperty', 0, 'enumProp']
const path = ['aProperty', '0', 'enumProp']
const expectedSchema = {
enum: [1, 2, 3]
}
Expand Down Expand Up @@ -294,7 +294,7 @@ describe('jsonSchemaUtils', () => {
}
}
}
const path = ['foo', 0]
const path = ['foo', '0']
assert.strictEqual(
findSchema(schema, { foo: fooSchema }, path),
fooSchema.definitions.some_def
Expand Down Expand Up @@ -328,7 +328,7 @@ describe('jsonSchemaUtils', () => {
}
}
}
const path = ['foo', 0, 'propA']
const path = ['foo', '0', 'propA']
assert.strictEqual(
findSchema(schema, { foo: fooSchema }, path),
fooSchema.definitions.some_def.properties.propA
Expand Down Expand Up @@ -363,7 +363,7 @@ describe('jsonSchemaUtils', () => {
}
}
}
const path = ['foo', 0, 'propA', 'propA1']
const path = ['foo', '2', 'propA', 'propA1']
assert.strictEqual(
findSchema(schema, { foo: fooSchema }, path),
fooSchema.definitions.some_def.properties.propA.properties.propA1
Expand Down
100 changes: 44 additions & 56 deletions src/lib/utils/jsonSchemaUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { JSONPath } from 'immutable-json-patch'
import type { JSONSchema, JSONSchemaDefinitions, JSONSchemaEnum } from '$lib/types'

/**
* Find enum options for given path in a JSONSchema
* @param {JSON} schema
* @param {JSON} [schemaDefinitions=undefined]
* @param {Path} path
* @returns {Array<any> | null}
*/
export function getJSONSchemaOptions(schema, schemaDefinitions, path) {
export function getJSONSchemaOptions(
schema: JSONSchema,
schemaDefinitions: JSONSchemaDefinitions | undefined,
path: JSONPath
): JSONSchemaEnum | null {
const schemaForPath = findSchema(schema, schemaDefinitions || {}, path)

return schemaForPath ? findEnum(schemaForPath) : null
Expand All @@ -16,18 +19,14 @@ export function getJSONSchemaOptions(schema, schemaDefinitions, path) {
* one of the schemas composites (`oneOf`, `anyOf`, `allOf`)
*
* Source: /~https://github.com/josdejong/jsoneditor/blob/develop/src/js/Node.js
*
* @param {Object} schema
* @return {Array | null} Returns the enum when found, null otherwise.
* @private
*/
export function findEnum(schema) {
if (schema.enum) {
return schema.enum
export function findEnum(schema: JSONSchema): JSONSchemaEnum | null {
if (Array.isArray(schema['enum'])) {
return schema['enum']
}

const composite = schema.oneOf || schema.anyOf || schema.allOf
if (composite) {
const composite = schema['oneOf'] || schema['anyOf'] || schema['allOf']
if (Array.isArray(composite)) {
const match = composite.filter((entry) => entry.enum)
if (match.length > 0) {
return match[0].enum
Expand All @@ -41,20 +40,13 @@ export function findEnum(schema) {
* Return the part of a JSON schema matching given path.
*
* Source: /~https://github.com/josdejong/jsoneditor/blob/develop/src/js/Node.js
*
* @param {JSON} topLevelSchema
* @param {JSON} schemaDefinitions
* @param {Array.<string | number>} path
* @param {Object} currentSchema
* @return {Object | null}
* @private
*/
export function findSchema(
topLevelSchema,
schemaDefinitions,
path,
topLevelSchema: JSONSchema,
schemaDefinitions: JSONSchemaDefinitions,
path: JSONPath,
currentSchema = topLevelSchema
) {
): JSONSchema | null {
const nextPath = path.slice(1, path.length)
const nextKey = path[0]

Expand All @@ -77,9 +69,9 @@ export function findSchema(
currentSchema = topLevelSchema
for (const segment of refPath) {
if (segment in currentSchema) {
currentSchema = currentSchema[segment]
currentSchema = currentSchema[segment] as JSONSchema
} else {
throw Error(`Unable to resovle reference ${ref}`)
throw Error(`Unable to resolve reference ${ref}`)
}
}
} else if (ref.match(/#\//g)?.length === 1) {
Expand Down Expand Up @@ -107,38 +99,34 @@ export function findSchema(
return currentSchema
}

if (typeof nextKey === 'string') {
if (
typeof currentSchema.properties === 'object' &&
currentSchema.properties !== null &&
nextKey in currentSchema.properties
) {
currentSchema = currentSchema.properties[nextKey]
return findSchema(topLevelSchema, schemaDefinitions, nextPath, currentSchema)
}
if (
typeof currentSchema.patternProperties === 'object' &&
currentSchema.patternProperties !== null
) {
for (const prop in currentSchema.patternProperties) {
if (nextKey.match(prop)) {
currentSchema = currentSchema.patternProperties[prop]
return findSchema(topLevelSchema, schemaDefinitions, nextPath, currentSchema)
}
}
}
if (typeof currentSchema.additionalProperties === 'object') {
currentSchema = currentSchema.additionalProperties
return findSchema(topLevelSchema, schemaDefinitions, nextPath, currentSchema)
}
continue
if (
typeof currentSchema.properties === 'object' &&
currentSchema.properties !== null &&
nextKey in currentSchema.properties
) {
currentSchema = currentSchema.properties[nextKey]
return findSchema(topLevelSchema, schemaDefinitions, nextPath, currentSchema)
}

if (
typeof nextKey === 'number' &&
typeof currentSchema.items === 'object' &&
currentSchema.items !== null
typeof currentSchema.patternProperties === 'object' &&
currentSchema.patternProperties !== null
) {
currentSchema = currentSchema.items
for (const prop in currentSchema.patternProperties) {
if (nextKey.match(prop)) {
currentSchema = currentSchema.patternProperties[prop]
return findSchema(topLevelSchema, schemaDefinitions, nextPath, currentSchema)
}
}
}

if (typeof currentSchema.additionalProperties === 'object') {
currentSchema = currentSchema.additionalProperties as JSONSchema
return findSchema(topLevelSchema, schemaDefinitions, nextPath, currentSchema)
}

if (typeof currentSchema.items === 'object' && currentSchema.items !== null) {
currentSchema = currentSchema.items as JSONSchema
return findSchema(topLevelSchema, schemaDefinitions, nextPath, currentSchema)
}
}
Expand Down

0 comments on commit 887bf23

Please sign in to comment.