Skip to content

Commit

Permalink
Add validate method to Expression/Constant
Browse files Browse the repository at this point in the history
  • Loading branch information
bkeepers committed Apr 5, 2023
1 parent 386e427 commit dce960e
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 25 deletions.
19 changes: 19 additions & 0 deletions packages/expressions/lib/constant.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useValidator } from './validate'

// Public: A constant value like a "string", number (1, 3.5), or boolean (true, false).
//
// Implements the same interface as Expression
Expand All @@ -9,4 +11,21 @@ export class Constant {
get args () {
return [this.value]
}

get schema () {
return { type: typeof this.value }
}

validate(schema = this.schema) {
const validator = useValidator()
const data = this.value
const valid = validator.validate(schema, data)
const errors = validator.errors
return { valid, errors, data }
}

matches(schema) {
const { valid } = this.validate(schema)
return valid
}
}
14 changes: 14 additions & 0 deletions packages/expressions/lib/expression.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { v4 as uuidv4 } from 'uuid'
import { Constant } from './constant'
import explorer from './explorer'
import { useValidator } from './validate'

// Simple model to transform this: `{ All: [{ Boolean: [true] }]`
// into this: `{ id: uuidv4(), name: 'All', args: [{ id: uuidv4(), name: 'Boolean', args: [true] }] }`
Expand Down Expand Up @@ -34,4 +35,17 @@ export class Expression {
get schema () {
return explorer.functions[this.name]
}

validate(schema = this.schema) {
const validator = useValidator()
const data = this.value
const valid = validator.validate(schema, data)
const errors = validator.errors
return { valid, errors, data }
}

matches(schema) {
const { valid } = this.validate(schema)
return valid
}
}
30 changes: 17 additions & 13 deletions packages/expressions/lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import Ajv from 'ajv'
import addFormats from 'ajv-formats'
import schemas, { BaseURI } from '../schemas'

const ajv = new Ajv({
schemas: Object.values(schemas),
useDefaults: true,
allErrors: true,
strict: true
})
addFormats(ajv)
const validator = ajv.getSchema(BaseURI)
export function useValidator(schemas = []) {
const ajv = new Ajv({
schemas,
useDefaults: true,
allErrors: true,
strict: true
})
addFormats(ajv)
return ajv
}

function coerceArgsToArray (object) {
if (object && typeof object === 'object') {
Expand All @@ -27,9 +29,11 @@ function coerceArgsToArray (object) {
}
}

export default (input) => {
const result = coerceArgsToArray(input)
const valid = validator(result)
const errors = validator.errors
return { valid, errors, result }
export const validator = useValidator(schemas)

export default (input, validate = validator.getSchema(BaseURI)) => {
const data = coerceArgsToArray(input)
const valid = validate(data)
const errors = validate.errors
return { valid, errors, data }
}
15 changes: 6 additions & 9 deletions packages/expressions/schemas/Duration.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,15 @@
{ "$ref": "schema.json#/definitions/number" },
{
"anyOf": [
{ "$ref": "#/definitions/unit" },
{
"type": "string",
"enum": ["seconds", "minutes", "hours", "days", "weeks", "months", "years"],
"default": "seconds"
},
{ "$ref": "schema.json#/definitions/function" }
]
}
],
"minItems": 2,
"maxItems": 2,
"definitions": {
"unit": {
"type": "string",
"enum": ["seconds", "minutes", "hours", "days", "weeks", "months", "years"],
"default": "seconds"
}
}
"maxItems": 2
}
16 changes: 13 additions & 3 deletions packages/expressions/schemas/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@
"title": "Constant",
"description": "A constant value can be a string, number or boolean",
"anyOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" }
{
"title": "String",
"type": "string"
},
{
"title": "Number",
"type": "number"
},
{
"title": "Boolean",
"type": "boolean"
}
]
},
"function": {
Expand Down Expand Up @@ -73,6 +82,7 @@
"minItems": 0
},
"arguments-two": {
"title": "Comparison",
"description": "An array with exactly two expressions",
"type": "array",
"items": { "$ref": "#/definitions/expression" },
Expand Down
34 changes: 34 additions & 0 deletions packages/expressions/test/constant.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, test, expect } from 'vitest'
import { Constant } from '../lib'

describe('Constant', () => {
describe('schema', () => {
test('returns `{ type: "string" }` for string value', () => {
expect(new Constant('string').schema).toEqual({ type: 'string' })
})

test('returns `{ type: "boolean" }` for boolean value', () => {
expect(new Constant(true).schema).toEqual({ type: 'boolean' })
})

test('returns `{ type: "number" }` for number value', () => {
expect(new Constant(42).schema).toEqual({ type: 'number' })
})
})

describe('validate', () => {
test('returns true for valid value', () => {
expect(new Constant(true).validate().valid).toBe(true)
})
})

describe('matches', () => {
test('returns true matching schema', () => {
expect(new Constant(true).matches({ type: 'boolean' })).toBe(true)
})

test('returns false for different schema', () => {
expect(new Constant('string').matches({ type: 'boolean' })).toBe(false)
})
})
})
27 changes: 27 additions & 0 deletions packages/expressions/test/expression.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, test, expect } from 'vitest'
import { Expression, Constant } from '../lib'
import { ajv } from '../lib/validate'

describe('Expression', () => {
describe('build', () => {
Expand Down Expand Up @@ -29,4 +30,30 @@ describe('Expression', () => {
expect(clone.value).toEqual({ All: [true] })
})
})

describe('validate', () => {
test('passes for valid expression', () => {
const expression = Expression.build({ All: [true] })
expect(expression.validate().valid).toBe(true)
})

test('fails for invalid expression', () => {
const expression = Expression.build({ Duration: [] })
expect(expression.validate().valid).toBe(false)
})
})

describe('matches', () => {
test('returns true matching schema', () => {
const expression = Expression.build({ Any: [true] })
const schema = ajv.getSchema("#/definitions/function")
console.log("DID IT WORK?", schema)
expect(expression.matches(schema)).toBe(true)
})

test('returns false for different schema', () => {
expect(new Constant('string').matches({ type: 'boolean' })).toBe(false)
})
})

})

0 comments on commit dce960e

Please sign in to comment.