Skip to content

Commit dce960e

Browse files
committed
Add validate method to Expression/Constant
1 parent 386e427 commit dce960e

File tree

7 files changed

+130
-25
lines changed

7 files changed

+130
-25
lines changed

packages/expressions/lib/constant.js

+19
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useValidator } from './validate'
2+
13
// Public: A constant value like a "string", number (1, 3.5), or boolean (true, false).
24
//
35
// Implements the same interface as Expression
@@ -9,4 +11,21 @@ export class Constant {
911
get args () {
1012
return [this.value]
1113
}
14+
15+
get schema () {
16+
return { type: typeof this.value }
17+
}
18+
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 }
25+
}
26+
27+
matches(schema) {
28+
const { valid } = this.validate(schema)
29+
return valid
30+
}
1231
}

packages/expressions/lib/expression.js

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { v4 as uuidv4 } from 'uuid'
22
import { Constant } from './constant'
33
import explorer from './explorer'
4+
import { useValidator } from './validate'
45

56
// Simple model to transform this: `{ All: [{ Boolean: [true] }]`
67
// into this: `{ id: uuidv4(), name: 'All', args: [{ id: uuidv4(), name: 'Boolean', args: [true] }] }`
@@ -34,4 +35,17 @@ export class Expression {
3435
get schema () {
3536
return explorer.functions[this.name]
3637
}
38+
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 }
45+
}
46+
47+
matches(schema) {
48+
const { valid } = this.validate(schema)
49+
return valid
50+
}
3751
}

packages/expressions/lib/validate.js

+17-13
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import Ajv from 'ajv'
22
import addFormats from 'ajv-formats'
33
import schemas, { BaseURI } from '../schemas'
44

5-
const ajv = new Ajv({
6-
schemas: Object.values(schemas),
7-
useDefaults: true,
8-
allErrors: true,
9-
strict: true
10-
})
11-
addFormats(ajv)
12-
const validator = ajv.getSchema(BaseURI)
5+
export function useValidator(schemas = []) {
6+
const ajv = new Ajv({
7+
schemas,
8+
useDefaults: true,
9+
allErrors: true,
10+
strict: true
11+
})
12+
addFormats(ajv)
13+
return ajv
14+
}
1315

1416
function coerceArgsToArray (object) {
1517
if (object && typeof object === 'object') {
@@ -27,9 +29,11 @@ function coerceArgsToArray (object) {
2729
}
2830
}
2931

30-
export default (input) => {
31-
const result = coerceArgsToArray(input)
32-
const valid = validator(result)
33-
const errors = validator.errors
34-
return { valid, errors, result }
32+
export const validator = useValidator(schemas)
33+
34+
export default (input, validate = validator.getSchema(BaseURI)) => {
35+
const data = coerceArgsToArray(input)
36+
const valid = validate(data)
37+
const errors = validate.errors
38+
return { valid, errors, data }
3539
}

packages/expressions/schemas/Duration.schema.json

+6-9
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,15 @@
88
{ "$ref": "schema.json#/definitions/number" },
99
{
1010
"anyOf": [
11-
{ "$ref": "#/definitions/unit" },
11+
{
12+
"type": "string",
13+
"enum": ["seconds", "minutes", "hours", "days", "weeks", "months", "years"],
14+
"default": "seconds"
15+
},
1216
{ "$ref": "schema.json#/definitions/function" }
1317
]
1418
}
1519
],
1620
"minItems": 2,
17-
"maxItems": 2,
18-
"definitions": {
19-
"unit": {
20-
"type": "string",
21-
"enum": ["seconds", "minutes", "hours", "days", "weeks", "months", "years"],
22-
"default": "seconds"
23-
}
24-
}
21+
"maxItems": 2
2522
}

packages/expressions/schemas/schema.json

+13-3
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@
1717
"title": "Constant",
1818
"description": "A constant value can be a string, number or boolean",
1919
"anyOf": [
20-
{ "type": "string" },
21-
{ "type": "number" },
22-
{ "type": "boolean" }
20+
{
21+
"title": "String",
22+
"type": "string"
23+
},
24+
{
25+
"title": "Number",
26+
"type": "number"
27+
},
28+
{
29+
"title": "Boolean",
30+
"type": "boolean"
31+
}
2332
]
2433
},
2534
"function": {
@@ -73,6 +82,7 @@
7382
"minItems": 0
7483
},
7584
"arguments-two": {
85+
"title": "Comparison",
7686
"description": "An array with exactly two expressions",
7787
"type": "array",
7888
"items": { "$ref": "#/definitions/expression" },
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { describe, test, expect } from 'vitest'
2+
import { Constant } from '../lib'
3+
4+
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' })
16+
})
17+
})
18+
19+
describe('validate', () => {
20+
test('returns true for valid value', () => {
21+
expect(new Constant(true).validate().valid).toBe(true)
22+
})
23+
})
24+
25+
describe('matches', () => {
26+
test('returns true matching schema', () => {
27+
expect(new Constant(true).matches({ type: 'boolean' })).toBe(true)
28+
})
29+
30+
test('returns false for different schema', () => {
31+
expect(new Constant('string').matches({ type: 'boolean' })).toBe(false)
32+
})
33+
})
34+
})

packages/expressions/test/expression.test.js

+27
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, test, expect } from 'vitest'
22
import { Expression, Constant } from '../lib'
3+
import { ajv } from '../lib/validate'
34

45
describe('Expression', () => {
56
describe('build', () => {
@@ -29,4 +30,30 @@ describe('Expression', () => {
2930
expect(clone.value).toEqual({ All: [true] })
3031
})
3132
})
33+
34+
describe('validate', () => {
35+
test('passes for valid expression', () => {
36+
const expression = Expression.build({ All: [true] })
37+
expect(expression.validate().valid).toBe(true)
38+
})
39+
40+
test('fails for invalid expression', () => {
41+
const expression = Expression.build({ Duration: [] })
42+
expect(expression.validate().valid).toBe(false)
43+
})
44+
})
45+
46+
describe('matches', () => {
47+
test('returns true matching schema', () => {
48+
const expression = Expression.build({ Any: [true] })
49+
const schema = ajv.getSchema("#/definitions/function")
50+
console.log("DID IT WORK?", schema)
51+
expect(expression.matches(schema)).toBe(true)
52+
})
53+
54+
test('returns false for different schema', () => {
55+
expect(new Constant('string').matches({ type: 'boolean' })).toBe(false)
56+
})
57+
})
58+
3259
})

0 commit comments

Comments
 (0)