Skip to content

Commit 0bda019

Browse files
committed
Add simple schema class to browse and validate schemas
1 parent 7ac10af commit 0bda019

14 files changed

+121
-210
lines changed

lib/constant.js

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useValidator } from './validate'
1+
import { schema } from './schemas'
22

33
// Public: A constant value like a "string", number (1, 3.5), or boolean (true, false).
44
//
@@ -12,20 +12,15 @@ export class Constant {
1212
return [this.value]
1313
}
1414

15-
get schema () {
16-
return { type: typeof this.value }
15+
get validator () {
16+
return schema.get('#/definitions/constant')
1717
}
1818

19-
validate(schema = this.schema) {
20-
const validator = useValidator()
21-
const data = this.value
22-
const valid = validator.validate(schema, data)
23-
const errors = validator.errors
24-
return { valid, errors, data }
19+
validate (validator = this.validator) {
20+
return schema.validate(this.value, validator)
2521
}
2622

27-
matches(schema) {
28-
const { valid } = this.validate(schema)
29-
return valid
23+
matches (localSchema) {
24+
return this.validate(localSchema).valid
3025
}
3126
}

lib/explorer.js

Lines changed: 0 additions & 45 deletions
This file was deleted.

lib/expression.js

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { v4 as uuidv4 } from 'uuid'
22
import { Constant } from './constant'
3-
import explorer from './explorer'
4-
import { useValidator } from './validate'
3+
import { schema } from './schemas'
4+
5+
function toArray (arg) {
6+
if (Array.isArray(arg)) {
7+
return arg
8+
} else if (arg === null) {
9+
return []
10+
} else {
11+
return [arg]
12+
}
13+
}
514

615
// Simple model to transform this: `{ All: [{ Boolean: [true] }]`
716
// into this: `{ id: uuidv4(), name: 'All', args: [{ id: uuidv4(), name: 'Boolean', args: [true] }] }`
@@ -12,11 +21,17 @@ export class Expression {
1221
}
1322

1423
if (typeof expression === 'object') {
24+
if (Object.keys(expression).length !== 1) {
25+
throw new TypeError(`Invalid expression: ${JSON.stringify(expression)}`)
26+
}
1527
const name = Object.keys(expression)[0]
16-
const args = expression[name].map(Expression.build)
28+
const args = toArray(expression[name]).map(Expression.build)
29+
1730
return new Expression({ name, args })
18-
} else {
31+
} else if (['number', 'string', 'boolean'].includes(typeof expression)) {
1932
return new Constant(expression)
33+
} else {
34+
throw new TypeError(`Invalid expression: ${JSON.stringify(expression)}`)
2035
}
2136
}
2237

@@ -32,20 +47,15 @@ export class Expression {
3247
return { [this.name]: this.args.map(arg => arg.value) }
3348
}
3449

35-
get schema () {
36-
return explorer.functions[this.name]
50+
get validator () {
51+
return schema.get('#')
3752
}
3853

39-
validate(schema = this.schema) {
40-
const validator = useValidator()
41-
const data = this.value
42-
const valid = validator.validate(schema, data)
43-
const errors = validator.errors
44-
return { valid, errors, data }
54+
validate (validator = this.validator) {
55+
return schema.validate(this.value, validator)
4556
}
4657

47-
matches(schema) {
48-
const { valid } = this.validate(schema)
49-
return valid
58+
matches (localSchema) {
59+
return this.validate(localSchema).valid
5060
}
5161
}

lib/index.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
export { default as schemas, BaseURI } from '../schemas'
1+
export { schema, schemas, BaseURI } from './schemas'
22
export { default as examples } from '../examples'
3-
export { default as explorer } from './explorer'
4-
export { default as validate } from './validate'
53
export { Expression } from './expression'
64
export { Constant } from './constant'

lib/schemas.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Ajv from 'ajv'
2+
import addFormats from 'ajv-formats'
3+
4+
const modules = import.meta.glob('../schemas/*.json', { eager: true, import: 'default' })
5+
export const schemas = Object.values(modules)
6+
export const BaseURI = modules['../schemas/schema.json'].$id
7+
8+
class Schema {
9+
constructor (schemas, baseURI) {
10+
this.baseURI = baseURI
11+
12+
this.ajv = new Ajv({
13+
schemas,
14+
useDefaults: true,
15+
allErrors: true,
16+
strict: true
17+
})
18+
addFormats(this.ajv)
19+
}
20+
21+
get (ref, baseURI = this.baseURI) {
22+
return this.ajv.getSchema(new URL(ref, baseURI).href)
23+
}
24+
25+
validate (data, validator = this.get('#')) {
26+
const valid = validator(data, schema)
27+
const errors = validator.errors
28+
return { valid, errors }
29+
}
30+
}
31+
32+
export const schema = new Schema(schemas, BaseURI)

lib/validate.js

Lines changed: 0 additions & 39 deletions
This file was deleted.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
"dependencies": {
2222
"ajv": "^8.12.0",
2323
"ajv-formats": "^2.1.1",
24-
"json-pointer": "^0.6.2",
2524
"uuid": "^9.0.0"
2625
},
2726
"devDependencies": {

schemas/index.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

schemas/schema.json

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
{
22
"$schema": "http://json-schema.org/draft-07/schema#",
33
"$id": "https://www.flippercloud.io/expressions/schema.json",
4-
"title": "Flipper Expressions",
5-
"description": "TODO",
6-
"$ref": "#/definitions/expression",
4+
"title": "Expression",
5+
"description": "An expression can be a Constant or a Function",
6+
"anyOf": [
7+
{ "$ref": "#/definitions/constant" },
8+
{ "$ref": "#/definitions/function" }
9+
],
710
"definitions": {
8-
"expression": {
9-
"title": "Expression",
10-
"description": "An expression can be a Function or a Constant",
11-
"anyOf": [
12-
{ "$ref": "#/definitions/constant" },
13-
{ "$ref": "#/definitions/function" }
14-
]
15-
},
1611
"constant": {
1712
"title": "Constant",
1813
"description": "A constant value can be a string, number or boolean",
@@ -78,21 +73,21 @@
7873
"arguments-n": {
7974
"description": "An array of expressions",
8075
"type": "array",
81-
"items": { "$ref": "#/definitions/expression" },
76+
"items": { "$ref": "#" },
8277
"minItems": 0
8378
},
8479
"arguments-two": {
8580
"title": "Comparison",
8681
"description": "An array with exactly two expressions",
8782
"type": "array",
88-
"items": { "$ref": "#/definitions/expression" },
83+
"items": { "$ref": "#" },
8984
"minItems": 2,
9085
"maxItems": 2
9186
},
9287
"argument": {
9388
"description": "An array with exactly one expression",
9489
"type": "array",
95-
"items": { "$ref": "#/definitions/expression" },
90+
"items": { "$ref": "#" },
9691
"minItems": 1,
9792
"maxItems": 1
9893
}

test/constant.test.js

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
11
import { describe, test, expect } from 'vitest'
2-
import { Constant } from '../lib'
2+
import { Constant, schema } from '../lib'
33

44
describe('Constant', () => {
5-
describe('schema', () => {
6-
test('returns `{ type: "string" }` for string value', () => {
7-
expect(new Constant('string').schema).toEqual({ type: 'string' })
8-
})
9-
10-
test('returns `{ type: "boolean" }` for boolean value', () => {
11-
expect(new Constant(true).schema).toEqual({ type: 'boolean' })
12-
})
13-
14-
test('returns `{ type: "number" }` for number value', () => {
15-
expect(new Constant(42).schema).toEqual({ type: 'number' })
5+
describe('validator', () => {
6+
test('returns Constant validator', () => {
7+
expect(new Constant('string').validator.schema.title).toEqual('Constant')
168
})
179
})
1810

1911
describe('validate', () => {
2012
test('returns true for valid value', () => {
2113
expect(new Constant(true).validate().valid).toBe(true)
14+
expect(new Constant(false).validate().valid).toBe(true)
15+
expect(new Constant('string').validate().valid).toBe(true)
16+
expect(new Constant(42).validate().valid).toBe(true)
17+
expect(new Constant(3.14).validate().valid).toBe(true)
2218
})
2319
})
2420

2521
describe('matches', () => {
26-
test('returns true matching schema', () => {
27-
expect(new Constant(true).matches({ type: 'boolean' })).toBe(true)
22+
test('returns true for matching validator', () => {
23+
const validator = schema.get('#/definitions/constant/anyOf/0')
24+
expect(new Constant('string').matches(validator)).toBe(true)
2825
})
2926

3027
test('returns false for different schema', () => {
31-
expect(new Constant('string').matches({ type: 'boolean' })).toBe(false)
28+
const validator = schema.get('#/definitions/constant/anyOf/0')
29+
expect(new Constant(true).matches(validator)).toBe(false)
3230
})
3331
})
3432
})

0 commit comments

Comments
 (0)