1
1
mirror of https://github.com/leon-ai/leon.git synced 2024-09-11 10:25:40 +03:00

Merge branch 'feat/add-typebox-ajv' into develop

This commit is contained in:
louistiti 2022-11-14 23:56:08 +08:00
commit cad6d7f725
No known key found for this signature in database
GPG Key ID: 7ECA3DD523793FE6
14 changed files with 496 additions and 517 deletions

View File

@ -185,13 +185,6 @@ You'll find a write-up on this [blog post](https://blog.getleon.ai/the-story-beh
<br>
<sub><sup>100 USD / month</sup></sub>
</td>
<td align="center" valign="middle" width="128">
<a href="https://github.com/phareal">
<img src="https://github.com/phareal.png?size=128" />
phareal
</a><br>
<sub><sup>30 USD / month</sup></sub>
</td>
<td align="center" valign="middle" width="128">
<a href="https://github.com/KeithIMyers">
<img src="https://github.com/KeithIMyers.png?size=128" />

View File

@ -1,12 +1,12 @@
{
"type": "",
"type": "service_account",
"project_id": "",
"private_key_id": "",
"private_key": "",
"client_email": "",
"client_email": "example@iam.gserviceaccount.com",
"client_id": "",
"auth_uri": "",
"token_uri": "",
"auth_provider_x509_cert_url": "",
"client_x509_cert_url": ""
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/"
}

View File

@ -1,32 +0,0 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
const amazonVoiceConfiguration = Type.Object({
credentials: Type.Object({
accessKeyId: Type.String(),
secretAccessKey: Type.String()
}),
region: Type.String()
})
const googleCloudVoiceConfiguration = Type.Object({
type: Type.Literal('service_account'),
project_id: Type.String(),
private_key_id: Type.String(),
private_key: Type.String(),
client_email: Type.String({ format: 'email' }),
client_id: Type.String(),
auth_uri: Type.String({ format: 'uri' }),
token_uri: Type.String({ format: 'uri' }),
auth_provider_x509_cert_url: Type.String({ format: 'uri' }),
client_x509_cert_url: Type.String({ format: 'uri' })
})
const watsonVoiceConfiguration = Type.Object({
apikey: Type.String(),
url: Type.String({ format: 'uri' })
})
export type AmazonVoiceConfiguration = Static<typeof amazonVoiceConfiguration>
export type GoogleCloudVoiceConfiguration = Static<
typeof googleCloudVoiceConfiguration
>
export type WatsonVoiceConfiguration = Static<typeof watsonVoiceConfiguration>

View File

@ -1,4 +1,4 @@
{
"apikey": "",
"url": ""
"url": "https://stream.watsonplatform.net/speech-to-text/api"
}

View File

@ -1,4 +1,4 @@
{
"apikey": "",
"url": ""
"url": "https://stream.watsonplatform.net/text-to-speech/api"
}

View File

@ -1,45 +0,0 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
const globalEntity = {
options: Type.Record(
Type.String(),
Type.Object({
synonyms: Type.Array(Type.String()),
data: Type.Record(Type.String(), Type.Array(Type.String()))
})
)
}
const globalResolver = {
name: Type.String(),
intents: Type.Record(
Type.String(),
Type.Object({
utterance_samples: Type.Array(Type.String()),
value: Type.Unknown()
})
)
}
const answers = {
answers: Type.Record(
Type.String(),
Type.Union([
Type.Record(Type.String(), Type.String()),
Type.Array(Type.String())
])
)
}
const globalEntitySchemaObject = Type.Strict(
Type.Object(globalEntity, { additionalProperties: false })
)
const globalResolverSchemaObject = Type.Strict(
Type.Object(globalResolver, { additionalProperties: false })
)
const answersSchemaObject = Type.Strict(
Type.Object(answers, { additionalProperties: false })
)
export type GlobalEntity = Static<typeof globalEntitySchemaObject>
export type GlobalResolver = Static<typeof globalResolverSchemaObject>
export type Answers = Static<typeof answersSchemaObject>

264
package-lock.json generated
View File

@ -20,7 +20,10 @@
"@nlpjs/core-loader": "^4.22.7",
"@nlpjs/lang-all": "^4.22.12",
"@nlpjs/nlp": "^4.22.17",
"@sinclair/typebox": "0.24.49",
"@segment/ajv-human-errors": "2.1.2",
"@sinclair/typebox": "0.25.8",
"ajv": "8.11.0",
"ajv-formats": "2.1.1",
"archiver": "^5.3.1",
"async": "^3.2.0",
"axios": "1.1.2",
@ -1644,6 +1647,28 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/@fast-csv/format": {
"version": "4.3.5",
"dev": true,
@ -1700,24 +1725,6 @@
"fast-uri": "^2.0.0"
}
},
"node_modules/@fastify/ajv-compiler/node_modules/ajv": {
"version": "8.11.0",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": {
"version": "1.0.0",
"license": "MIT"
},
"node_modules/@fastify/deepmerge": {
"version": "1.1.0",
"license": "MIT"
@ -3311,10 +3318,18 @@
"version": "1.1.0",
"license": "BSD-3-Clause"
},
"node_modules/@segment/ajv-human-errors": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@segment/ajv-human-errors/-/ajv-human-errors-2.1.2.tgz",
"integrity": "sha512-d1uQndRFLRO01+xW1y5m+joxDgHf5SLJ70YCY2ArLoo2FJ50o6AoX2mEbuGvnKz/IdwnvDugm9Ti3mZQkW1OoA==",
"peerDependencies": {
"ajv": "^8.0.0"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.24.49",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.49.tgz",
"integrity": "sha512-qWgJVeCThMWTJSAZHyHDlHBkJY0LARFoq/96RH4oIFAwJptMwB3Isq62c4zRVRIAF2r4RMOc2WOhtOLj5C4InA=="
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.8.tgz",
"integrity": "sha512-/7GXgMOCfnxpIov52cPetqQ7bxRBaTBJAgp04Se2UQB1J0vUfEOIMpn63cLc3S5JXDUflCWxELKDV8eiPpmUTQ=="
},
"node_modules/@sinonjs/commons": {
"version": "1.8.5",
@ -4107,38 +4122,9 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats": {
"version": "2.1.1",
"license": "MIT",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ajv-formats/node_modules/ajv": {
"version": "8.11.0",
"license": "MIT",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@ -4150,9 +4136,21 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"license": "MIT"
"node_modules/ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ansi-colors": {
"version": "4.1.3",
@ -6607,6 +6605,22 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/eslint/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint/node_modules/eslint-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
@ -6634,6 +6648,12 @@
"node": ">=10"
}
},
"node_modules/eslint/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"node_modules/espree": {
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
@ -6952,24 +6972,6 @@
"rfdc": "^1.2.0"
}
},
"node_modules/fast-json-stringify/node_modules/ajv": {
"version": "8.11.0",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/fast-json-stringify/node_modules/json-schema-traverse": {
"version": "1.0.0",
"license": "MIT"
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"dev": true,
@ -10080,9 +10082,9 @@
"license": "MIT"
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"dev": true,
"license": "MIT"
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@ -15544,6 +15546,26 @@
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
}
}
},
"@fast-csv/format": {
@ -15598,20 +15620,6 @@
"ajv": "^8.11.0",
"ajv-formats": "^2.1.1",
"fast-uri": "^2.0.0"
},
"dependencies": {
"ajv": {
"version": "8.11.0",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0"
}
}
},
"@fastify/deepmerge": {
@ -16839,10 +16847,16 @@
"@protobufjs/utf8": {
"version": "1.1.0"
},
"@segment/ajv-human-errors": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@segment/ajv-human-errors/-/ajv-human-errors-2.1.2.tgz",
"integrity": "sha512-d1uQndRFLRO01+xW1y5m+joxDgHf5SLJ70YCY2ArLoo2FJ50o6AoX2mEbuGvnKz/IdwnvDugm9Ti3mZQkW1OoA==",
"requires": {}
},
"@sinclair/typebox": {
"version": "0.24.49",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.49.tgz",
"integrity": "sha512-qWgJVeCThMWTJSAZHyHDlHBkJY0LARFoq/96RH4oIFAwJptMwB3Isq62c4zRVRIAF2r4RMOc2WOhtOLj5C4InA=="
"version": "0.25.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.8.tgz",
"integrity": "sha512-/7GXgMOCfnxpIov52cPetqQ7bxRBaTBJAgp04Se2UQB1J0vUfEOIMpn63cLc3S5JXDUflCWxELKDV8eiPpmUTQ=="
},
"@sinonjs/commons": {
"version": "1.8.5",
@ -17338,33 +17352,22 @@
}
},
"ajv": {
"version": "6.12.6",
"dev": true,
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
"requires": {
"ajv": "^8.0.0"
},
"dependencies": {
"ajv": {
"version": "8.11.0",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0"
}
}
},
"ansi-colors": {
@ -18715,6 +18718,18 @@
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"eslint-utils": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
@ -18731,6 +18746,12 @@
"dev": true
}
}
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
}
}
},
@ -19139,20 +19160,6 @@
"fast-deep-equal": "^3.1.3",
"fast-uri": "^2.1.0",
"rfdc": "^1.2.0"
},
"dependencies": {
"ajv": {
"version": "8.11.0",
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
}
},
"json-schema-traverse": {
"version": "1.0.0"
}
}
},
"fast-levenshtein": {
@ -21282,8 +21289,9 @@
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"dev": true
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",

View File

@ -75,7 +75,10 @@
"@nlpjs/core-loader": "^4.22.7",
"@nlpjs/lang-all": "^4.22.12",
"@nlpjs/nlp": "^4.22.17",
"@sinclair/typebox": "0.24.49",
"@segment/ajv-human-errors": "2.1.2",
"@sinclair/typebox": "0.25.8",
"ajv": "8.11.0",
"ajv-formats": "2.1.1",
"archiver": "^5.3.1",
"async": "^3.2.0",
"axios": "1.1.2",

25
server/src/ajv.ts Normal file
View File

@ -0,0 +1,25 @@
import addFormats from 'ajv-formats'
import Ajv from 'ajv'
export const ajv = addFormats(
new Ajv({
allErrors: true,
verbose: true
}),
[
'date-time',
'time',
'date',
'email',
'hostname',
'ipv4',
'ipv6',
'uri',
'uri-reference',
'uuid',
'uri-template',
'json-pointer',
'relative-json-pointer',
'regex'
]
)

View File

@ -1,8 +1,9 @@
import fs from 'node:fs'
import path from 'node:path'
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { AggregateAjvError } from '@segment/ajv-human-errors'
import { ajv } from '@/ajv'
import {
amazonVoiceConfiguration,
googleCloudVoiceConfiguration,
@ -31,6 +32,27 @@ import { SkillDomainHelper } from '@/helpers/skill-domain-helper'
import { VOICE_CONFIG_PATH, GLOBAL_DATA_PATH } from '@/constants'
import { getGlobalEntitiesPath, getGlobalResolversPath } from '@/utilities'
interface ObjectUnknown {
[key: string]: unknown
}
const validateSchema = (
schema: ObjectUnknown,
contentToValidate: ObjectUnknown,
customErrorMesage: string
): void => {
const validate = ajv.compile(schema)
const isValid = validate(contentToValidate)
if (!isValid) {
LogHelper.error(customErrorMesage)
const errors = new AggregateAjvError(validate.errors ?? [])
for (const error of errors) {
LogHelper.error(error.message)
}
process.exit(1)
}
}
/**
* Pre-checking
* Ensure JSON files are correctly formatted
@ -57,21 +79,19 @@ const GLOBAL_DATA_SCHEMAS = {
const voiceConfigFiles = (
await fs.promises.readdir(VOICE_CONFIG_PATH)
).filter((file) => file.endsWith('.json') && !file.includes('.sample.'))
).filter((file) => file.endsWith('.json'))
for (const file of voiceConfigFiles) {
const voiceConfigPath = path.join(VOICE_CONFIG_PATH, file)
const config: VoiceConfiguration = JSON.parse(
await fs.promises.readFile(path.join(VOICE_CONFIG_PATH, file), 'utf8')
await fs.promises.readFile(voiceConfigPath, 'utf8')
)
const [configName] = file.split('.') as [keyof typeof VOICE_CONFIG_SCHEMAS]
const result = TypeCompiler.Compile(VOICE_CONFIG_SCHEMAS[configName])
const errors = [...result.Errors(config)]
if (errors.length > 0) {
LogHelper.error(`The voice configuration schema "${file}" is not valid:`)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
validateSchema(
VOICE_CONFIG_SCHEMAS[configName],
config,
`The voice configuration schema "${voiceConfigPath}" is not valid:`
)
}
LogHelper.success('Voice configuration schemas checked')
@ -91,17 +111,15 @@ const GLOBAL_DATA_SCHEMAS = {
).filter((file) => file.endsWith('.json'))
for (const file of globalEntityFiles) {
const globalEntityPath = path.join(globalEntitiesPath, file)
const globalEntity: GlobalEntity = JSON.parse(
await fs.promises.readFile(path.join(globalEntitiesPath, file), 'utf8')
await fs.promises.readFile(globalEntityPath, 'utf8')
)
validateSchema(
globalEntitySchemaObject,
globalEntity,
`The global entity schema "${globalEntityPath}" is not valid:`
)
const result = TypeCompiler.Compile(globalEntitySchemaObject)
const errors = [...result.Errors(globalEntity)]
if (errors.length > 0) {
LogHelper.error(`The global entity schema "${file}" is not valid:`)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
}
/**
@ -113,37 +131,32 @@ const GLOBAL_DATA_SCHEMAS = {
).filter((file) => file.endsWith('.json'))
for (const file of globalResolverFiles) {
const globalResolverPath = path.join(globalResolversPath, file)
const globalResolver: GlobalResolver = JSON.parse(
await fs.promises.readFile(path.join(globalResolversPath, file), 'utf8')
await fs.promises.readFile(globalResolverPath, 'utf8')
)
validateSchema(
globalResolverSchemaObject,
globalResolver,
`The global resolver schema "${globalResolverPath}" is not valid:`
)
const result = TypeCompiler.Compile(globalResolverSchemaObject)
const errors = [...result.Errors(globalResolver)]
if (errors.length > 0) {
LogHelper.error(`The global resolver schema "${file}" is not valid:`)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
}
/**
* Global answers checking
*/
const globalAnswersPath = path.join(GLOBAL_DATA_PATH, lang, 'answers.json')
const answers: GlobalAnswers = JSON.parse(
await fs.promises.readFile(
path.join(GLOBAL_DATA_PATH, lang, 'answers.json'),
globalAnswersPath,
'utf8'
)
)
const result = TypeCompiler.Compile(GLOBAL_DATA_SCHEMAS.answers)
const errors = [...result.Errors(answers)]
if (errors.length > 0) {
LogHelper.error(`The global answers schema "answers.json" is not valid:`)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
validateSchema(
GLOBAL_DATA_SCHEMAS.answers,
answers,
`The global answers schema "${globalAnswersPath}" is not valid:`
)
}
LogHelper.success('Global data schemas checked')
@ -162,14 +175,11 @@ const GLOBAL_DATA_SCHEMAS = {
const domainObject: Domain = JSON.parse(
await fs.promises.readFile(pathToDomain, 'utf8')
)
const domainResult = TypeCompiler.Compile(domainSchemaObject)
const domainErrors = [...domainResult.Errors(domainObject)]
if (domainErrors.length > 0) {
LogHelper.error(`The domain schema "${pathToDomain}" is not valid:`)
LogHelper.error(JSON.stringify(domainErrors, null, 2))
process.exit(1)
}
validateSchema(
domainSchemaObject,
domainObject,
`The domain schema "${pathToDomain}" is not valid:`
)
const skillKeys = Object.keys(currentDomain.skills)
@ -184,14 +194,11 @@ const GLOBAL_DATA_SCHEMAS = {
const skillObject: Skill = JSON.parse(
await fs.promises.readFile(pathToSkill, 'utf8')
)
const skillResult = TypeCompiler.Compile(skillSchemaObject)
const skillErrors = [...skillResult.Errors(skillObject)]
if (skillErrors.length > 0) {
LogHelper.error(`The skill schema "${pathToSkill}" is not valid:`)
LogHelper.error(JSON.stringify(skillErrors, null, 2))
process.exit(1)
}
validateSchema(
skillSchemaObject,
skillObject,
`The skill schema "${pathToSkill}" is not valid:`
)
/**
* Skills config checking
@ -206,16 +213,11 @@ const GLOBAL_DATA_SCHEMAS = {
const skillConfig: SkillConfig = JSON.parse(
await fs.promises.readFile(skillConfigPath, 'utf8')
)
const result = TypeCompiler.Compile(skillConfigSchemaObject)
const errors = [...result.Errors(skillConfig)]
if (errors.length > 0) {
LogHelper.error(
`The skill config schema "${skillConfigPath}" is not valid:`
)
LogHelper.error(JSON.stringify(errors, null, 2))
process.exit(1)
}
validateSchema(
skillConfigSchemaObject,
skillConfig,
`The skill config schema "${skillConfigPath}" is not valid:`
)
}
}
}

View File

@ -1,34 +1,57 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
export const globalEntitySchemaObject = Type.Object({
options: Type.Record(
Type.String(),
Type.Object({
synonyms: Type.Array(Type.String()),
data: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String())))
})
export const globalEntitySchemaObject = Type.Strict(
Type.Object(
{
options: Type.Record(
Type.String(),
Type.Object(
{
synonyms: Type.Array(Type.String()),
data: Type.Optional(
Type.Record(Type.String(), Type.Array(Type.String()))
)
},
{ additionalProperties: false }
)
)
},
{ additionalProperties: false }
)
})
export const globalResolverSchemaObject = Type.Object({
name: Type.String(),
intents: Type.Record(
Type.String(),
Type.Object({
utterance_samples: Type.Array(Type.String()),
value: Type.Unknown()
})
)
export const globalResolverSchemaObject = Type.Strict(
Type.Object(
{
name: Type.String(),
intents: Type.Record(
Type.String(),
Type.Object(
{
utterance_samples: Type.Array(Type.String()),
value: Type.Unknown()
},
{ additionalProperties: false }
)
)
},
{ additionalProperties: false }
)
})
export const globalAnswersSchemaObject = Type.Object({
answers: Type.Record(
Type.String(),
Type.Union([
Type.Record(Type.String(), Type.String()),
Type.Array(Type.String())
])
)
export const globalAnswersSchemaObject = Type.Strict(
Type.Object(
{
answers: Type.Record(
Type.String(),
Type.Union([
Type.Record(Type.String(), Type.String()),
Type.Array(Type.String())
])
)
},
{ additionalProperties: false }
)
})
)
export type GlobalEntity = Static<typeof globalEntitySchemaObject>
export type GlobalResolver = Static<typeof globalResolverSchemaObject>

View File

@ -1,6 +1,8 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
import { globalResolverSchemaObject } from '@/schemas/global-data-schemas'
const skillBridges = [Type.Literal('python'), Type.Null()]
const skillActionTypes = [Type.Literal('logic'), Type.Literal('dialog')]
const skillDataTypes = [
@ -10,109 +12,184 @@ const skillDataTypes = [
]
const skillCustomEntityTypes = [
Type.Array(
Type.Object({
type: Type.Literal('trim'),
name: Type.String({ minLength: 1 }),
conditions: Type.Array(
Type.Object({
type: Type.Union([
Type.Literal('between'),
Type.Literal('after'),
Type.Literal('after_first'),
Type.Literal('after_last'),
Type.Literal('before'),
Type.Literal('before_first'),
Type.Literal('before_last')
]),
from: Type.Optional(
Type.Union([
Type.Array(Type.String({ minLength: 1 })),
Type.String({ minLength: 1 })
])
),
to: Type.Optional(
Type.Union([
Type.Array(Type.String({ minLength: 1 })),
Type.String({ minLength: 1 })
])
Type.Object(
{
type: Type.Literal('trim'),
name: Type.String({ minLength: 1 }),
conditions: Type.Array(
Type.Object(
{
type: Type.Union([
Type.Literal('between'),
Type.Literal('after'),
Type.Literal('after_first'),
Type.Literal('after_last'),
Type.Literal('before'),
Type.Literal('before_first'),
Type.Literal('before_last')
]),
from: Type.Optional(
Type.Union([
Type.Array(Type.String({ minLength: 1 })),
Type.String({ minLength: 1 })
])
),
to: Type.Optional(
Type.Union([
Type.Array(Type.String({ minLength: 1 })),
Type.String({ minLength: 1 })
])
)
},
{ additionalProperties: false }
)
})
)
})
)
},
{ additionalProperties: false }
)
),
Type.Array(
Type.Object({
type: Type.Literal('regex'),
name: Type.String({ minLength: 1 }),
regex: Type.String({ minLength: 1 })
})
Type.Object(
{
type: Type.Literal('regex'),
name: Type.String({ minLength: 1 }),
regex: Type.String({ minLength: 1 })
},
{ additionalProperties: false }
)
),
Type.Array(
Type.Object({
type: Type.Literal('enum'),
name: Type.String(),
options: Type.Record(
Type.String({ minLength: 1 }),
Type.Object({
synonyms: Type.Array(Type.String({ minLength: 1 }))
})
)
})
Type.Object(
{
type: Type.Literal('enum'),
name: Type.String(),
options: Type.Record(
Type.String({ minLength: 1 }),
Type.Object({
synonyms: Type.Array(Type.String({ minLength: 1 }))
})
)
},
{ additionalProperties: false }
)
)
]
export const domainSchemaObject = Type.Object({
name: Type.String({ minLength: 1 })
})
export const skillSchemaObject = Type.Object({
name: Type.String({ minLength: 1 }),
bridge: Type.Union(skillBridges),
version: Type.String({ minLength: 1 }),
description: Type.String({ minLength: 1 }),
author: Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.Optional(Type.String({ minLength: 1, maxLength: 254 })),
url: Type.Optional(Type.String({ minLength: 1, maxLength: 255 }))
})
})
export const skillConfigSchemaObject = Type.Object({
variables: Type.Optional(Type.Record(Type.String(), Type.String())),
actions: Type.Record(
Type.String(),
Type.Object({
type: Type.Union(skillActionTypes),
loop: Type.Optional(
Type.Object({
expected_item: Type.Object({
type: Type.Union(skillDataTypes),
name: Type.String()
})
})
),
utterance_samples: Type.Optional(Type.Array(Type.String())),
answers: Type.Optional(Type.Array(Type.String())),
unknown_answers: Type.Optional(Type.Array(Type.String())),
suggestions: Type.Optional(Type.Array(Type.String())),
slots: Type.Optional(
Type.Array(
Type.Object({
name: Type.String(),
item: Type.Object({
type: Type.Union(skillDataTypes),
name: Type.String()
}),
questions: Type.Array(Type.String()),
suggestions: Type.Optional(Type.Array(Type.String()))
})
export const domainSchemaObject = Type.Strict(
Type.Object(
{
name: Type.String({ minLength: 1 })
},
{ additionalProperties: false }
)
)
export const skillSchemaObject = Type.Strict(
Type.Object(
{
name: Type.String({ minLength: 1 }),
bridge: Type.Union(skillBridges),
version: Type.String({ minLength: 1 }),
description: Type.String({ minLength: 1 }),
author: Type.Object(
{
name: Type.String({ minLength: 1 }),
email: Type.Optional(
Type.String({ minLength: 1, maxLength: 254, format: 'email' })
),
url: Type.Optional(
Type.String({ minLength: 1, maxLength: 255, format: 'uri' })
)
},
{ additionalProperties: false }
)
},
{ additionalProperties: false }
)
)
export const skillConfigSchemaObject = Type.Strict(
Type.Object(
{
variables: Type.Optional(Type.Record(Type.String(), Type.String())),
actions: Type.Record(
Type.String(),
Type.Object(
{
type: Type.Union(skillActionTypes),
loop: Type.Optional(
Type.Object(
{
expected_item: Type.Object({
type: Type.Union(skillDataTypes),
name: Type.String()
})
},
{ additionalProperties: false }
)
),
http_api: Type.Optional(
Type.Object(
{
entities: Type.Array(
Type.Object(
{
entity: Type.String(),
resolution: Type.Array(Type.String())
},
{ additionalProperties: false }
)
)
},
{ additionalProperties: false }
)
),
utterance_samples: Type.Optional(Type.Array(Type.String())),
answers: Type.Optional(Type.Array(Type.String())),
unknown_answers: Type.Optional(Type.Array(Type.String())),
suggestions: Type.Optional(Type.Array(Type.String())),
slots: Type.Optional(
Type.Array(
Type.Object(
{
name: Type.String(),
item: Type.Object(
{
type: Type.Union(skillDataTypes),
name: Type.String()
},
{ additionalProperties: false }
),
questions: Type.Array(Type.String()),
suggestions: Type.Optional(Type.Array(Type.String()))
},
{ additionalProperties: false }
)
)
),
entities: Type.Optional(Type.Union(skillCustomEntityTypes)),
next_action: Type.Optional(Type.String())
},
{ additionalProperties: false }
)
),
entities: Type.Optional(Type.Union(skillCustomEntityTypes)),
next_action: Type.Optional(Type.String())
})
),
answers: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String()))),
entities: Type.Optional(Type.Record(Type.String(), Type.String()))
})
answers: Type.Optional(
Type.Record(Type.String(), Type.Array(Type.String()))
),
entities: Type.Optional(Type.Record(Type.String(), Type.String())),
resolvers: Type.Optional(
Type.Record(
Type.String(),
Type.Object(
{
intents: globalResolverSchemaObject.properties.intents
},
{ additionalProperties: false }
)
)
)
},
{ additionalProperties: false }
)
)
export type Domain = Static<typeof domainSchemaObject>
export type Skill = Static<typeof skillSchemaObject>

View File

@ -1,29 +1,44 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
export const amazonVoiceConfiguration = Type.Object({
credentials: Type.Object({
accessKeyId: Type.String(),
secretAccessKey: Type.String()
}),
region: Type.String()
})
export const googleCloudVoiceConfiguration = Type.Object({
type: Type.Literal('service_account'),
project_id: Type.String(),
private_key_id: Type.String(),
private_key: Type.String(),
client_email: Type.String(),
client_id: Type.String(),
auth_uri: Type.String(),
token_uri: Type.String(),
auth_provider_x509_cert_url: Type.String(),
client_x509_cert_url: Type.String()
})
export const watsonVoiceConfiguration = Type.Object({
apikey: Type.String(),
url: Type.String()
})
export const amazonVoiceConfiguration = Type.Strict(
Type.Object(
{
credentials: Type.Object({
accessKeyId: Type.String(),
secretAccessKey: Type.String()
}),
region: Type.String()
},
{ additionalProperties: false }
)
)
export const googleCloudVoiceConfiguration = Type.Strict(
Type.Object(
{
type: Type.Literal('service_account'),
project_id: Type.String(),
private_key_id: Type.String(),
private_key: Type.String(),
client_email: Type.String({ format: 'email' }),
client_id: Type.String(),
auth_uri: Type.String({ format: 'uri' }),
token_uri: Type.String({ format: 'uri' }),
auth_provider_x509_cert_url: Type.String({ format: 'uri' }),
client_x509_cert_url: Type.String({ format: 'uri' })
},
{ additionalProperties: false }
)
)
export const watsonVoiceConfiguration = Type.Strict(
Type.Object(
{
apikey: Type.String(),
url: Type.String({ format: 'uri' })
},
{ additionalProperties: false }
)
)
export type AmazonVoiceConfiguration = Static<typeof amazonVoiceConfiguration>
export type GoogleCloudVoiceConfiguration = Static<

View File

@ -1,90 +0,0 @@
import type { Static } from '@sinclair/typebox'
import { Type } from '@sinclair/typebox'
const domainSchema = {
name: Type.String({ minLength: 1 })
}
const skillBridges = [Type.Literal('python')]
const skillSchema = {
name: Type.String({ minLength: 1 }),
bridge: Type.Union(skillBridges),
version: Type.String({ minLength: 1 }),
description: Type.String({ minLength: 1 }),
author: Type.Object({
name: Type.String({ minLength: 1 }),
email: Type.String({ minLength: 1, maxLength: 254, format: 'email' }),
url: Type.String({ minLength: 1, maxLength: 255, format: 'uri' })
})
}
const skillActionTypes = [Type.Literal('logic'), Type.Literal('dialog')]
const skillDataTypes = [
Type.Literal('skill_resolver'),
Type.Literal('global_resolver'),
Type.Literal('entity')
]
const skillConfigSchema = {
variables: Type.Optional(Type.Record(Type.String(), Type.String())),
actions: Type.Record(
Type.String(),
Type.Object({
type: Type.Union(skillActionTypes),
loop: Type.Optional(
Type.Object({
expected_item: Type.Object({
type: Type.Union(skillDataTypes),
name: Type.String()
})
})
),
utterance_samples: Type.Optional(Type.Array(Type.String())),
answers: Type.Optional(Type.Array(Type.String())),
unknown_answers: Type.Optional(Type.Array(Type.String())),
suggestions: Type.Optional(Type.Array(Type.String())),
slots: Type.Optional(
Type.Array(
Type.Object({
name: Type.String(),
item: Type.Object({
type: Type.Union(skillDataTypes),
name: Type.String()
}),
questions: Type.Array(Type.String()),
suggestions: Type.Optional(Type.Array(Type.String()))
})
)
),
entities: Type.Optional(
Type.Array(
Type.Object({
type: Type.Literal('enum'),
name: Type.String(),
options: Type.Record(
Type.String(),
Type.Object({
synonyms: Type.Array(Type.String())
})
)
})
)
),
next_action: Type.Optional(Type.String())
})
),
answers: Type.Optional(Type.Record(Type.String(), Type.Array(Type.String()))),
entities: Type.Optional(Type.Record(Type.String(), Type.String()))
}
const domainSchemaObject = Type.Strict(
Type.Object(domainSchema, { additionalProperties: false })
)
const skillSchemaObject = Type.Strict(
Type.Object(skillSchema, { additionalProperties: false })
)
const skillConfigSchemaObject = Type.Strict(
Type.Object(skillConfigSchema, { additionalProperties: false })
)
export type Domain = Static<typeof domainSchemaObject>
export type Skill = Static<typeof skillSchemaObject>
export type SkillConfig = Static<typeof skillConfigSchemaObject>
export type SkillBridge = Static<typeof skillSchema.bridge>