Skip to content

Commit

Permalink
Merge pull request #82 from RabotaRu/jsonschema
Browse files Browse the repository at this point in the history
JSON Schema
  • Loading branch information
rpiontik authored Aug 24, 2022
2 parents ff8b7db + c0ca700 commit ecf038d
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 36 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@asyncapi/web-component": "0.24.23",
"@mdi/font": "5.9.55",
"ajv": "8.11.0",
"ajv-i18n": "4.2.0",
"axios": "0.21.1",
"dateformat": "3.0.3",
"jsonata": "1.8.6",
Expand Down
52 changes: 31 additions & 21 deletions public/documentation/arch/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,41 @@ rules:
"uid": "expert-component-" & id, /* Уникальный идентификатор выявленной ошибки */
"location": "/architect/components/" & id, /* Ссылка на расположение объекта ошибки */
"correction": "Укажите эксперта по компоненту", /* Рекомендации как исправить проблему */
"description": "Компоненты L1 должены иметь сведения об экспертах."
"description": "Компоненты L1 должны иметь сведения об экспертах."
}])
dochub.fields.source: # Валидатор контролирует заполнение поля указывающего на исходник для домена DocHub
# используя для этого JSON schema (https://json-schema.org/)
title: Не указан файл исходного кода # Название валидатора
source: > # Источник данных об ошибках. В данном случае JSONata запрос
([([
components.$spread().( /* Сканируем все компоненты */
$ID := $keys()[0];
{ /* Генерируем массив признаков проблем */
"isComponent": *.entity = "component", /* Это компонент */
"isDocHubDomain": $boolean($match($ID, /dochub\.front.*/)), /* в домене DocHub */
"isSourceEmpty": $not($boolean("" & *.source)), /* и поле source не заполнено */
"id": $ID
schema: # JSON Schema
type: object
properties:
source:
type: string
required:
- source
additionalProperties: true
source: > # Источник данных об ошибках
(
/* Создаем валидатор JSON schema */
$validator := $jsonschema($self.schema); /* Схему валидата получаем из контекста отклонения*/
/* Формируем базу для проверки */
([([
components.$spread().( /* Сканируем все компоненты */
$ID := $keys()[0];
{ /* Генерируем массив признаков проблем */
"isComponent": *.entity = "component", /* Это компонент */
"isDocHubDomain": $boolean($match($ID, /dochub\.front.*/)), /* в домене DocHub */
"id": $ID, /* Запоминаем идентификатор компонента */
"isvalid": $validator($.*) /* Валидируем компонент по схеме */
}
)
][isDocHubDomain and isSourceEmpty and isComponent]).{ /* Отбираем все компоненты где поле 'source' пустое*/
"uid": "source-component-" & id, /* Уникальный идентификатор выявленной ошибки */
"location": "/architect/components/" & id, /* Ссылка на расположение объекта ошибки */
"correction": "Укажите где расположен файл с исходными кодами", /* Рекомендации как исправить проблему */
"description": "Компоненты должены иметь сведения о файлах исходного кода в которых они реализуются.
Если необходимо исключить для данного компонента эту информацию,
укажите идентификатор ошибки в разделе 'exceptions' с описанием причиныsss
",
"isDocHubDomain":isDocHubDomain
}])
)
][isDocHubDomain and isComponent and isvalid != true]).isvalid.{ /* Генерируем отклонения по выявленным нарушениям */
"uid": $.params.missingProperty & "-component-" & %.id, /* Уникальный идентификатор выявленной ошибки */
"location": "/architect/components/" & %.id, /* Ссылка на расположение объекта ошибки */
"correction": "Заполните необходимые поля", /* Рекомендации как исправить проблему */
"description": message
}])
)
exceptions: # Исключения для валидаторов
"source-component-dochub.front.spa.blank.doc.markdown": # UID ошибки для исключения
reason: > # Причина исключения
Expand Down
44 changes: 44 additions & 0 deletions public/documentation/docs/manual/jsonata.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,50 @@ $merge([
}
```

### $jsonschema()

Сигнатура: $jsonschema(object)

На базе [JSON schema](https://json-schema.org/) создает валидатор, который можно использовать
для проверки структуры данных. Реализация функции основана на NPM пакете [ajv](https://www.npmjs.com/package/ajv).

В параметре **object** передается схема. Возвращается функция-валидатор. Пример использования:

```json
(
/* Создаем валидатор */
$validator := $jsonschema({
"type": "object",
"properties": {
"foo": { "type": "integer" },
"bar": { "type": "string"}
},
"required": ["foo"],
"additionalProperties": false
});

/* Проверяем структуру */
$validator({
"foo": "error",
"bar": "abc"
});
)
```

Результат:
```json
[
{
"instancePath": "/foo",
"schemaPath": "#/properties/foo/type",
"keyword": "type",
"params": {
"type": "integer"
},
"message": "должно быть integer"
}
]
```

## Дополнительные переменные

Expand Down
47 changes: 47 additions & 0 deletions public/documentation/docs/manual/rules/validators.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
Каждый валидатор имеет свой уникальный, структурированный идентификатор. На основании идентификаторов строится
[дерево](/problems), которое наглядно определяет где обнаружена проблема.

## Простой валидатор

Пример описания простого валидатора:
```yaml
...
Expand Down Expand Up @@ -59,4 +61,49 @@ rules:
* ***description*** - Опциональное поле. Описание причины ошибки.
* ***cause*** - Опциональное поле. Идентификатор или URL документа, на основании которого создан валидатор.

## Валидатор с использованием JSON Schema

Для контроля метамодели больше подходит специализированный словарь, который позволяет аннотировать и проверять
структуру данных архитектуры. В качестве такого словаря используется [JSON Schema](https://json-schema.org/),
реализованная NPM пакетом [avj](https://www.npmjs.com/package/ajv).

Пример описания отклонения:
```yaml
dochub.fields.source: # Валидатор контролирует заполнение поля указывающего на исходник для домена DocHub
# используя для этого JSON schema (https://json-schema.org/)
title: Не указан файл исходного кода # Название валидатора
schema: # JSON Schema
type: object
properties:
source:
type: string
required:
- source
additionalProperties: true
source: > # Источник данных об ошибках
(
/* Создаем валидатор JSON schema */
$validator := $jsonschema($self.schema); /* Схему валидата получаем из контекста отклонения*/
/* Формируем базу для проверки */
([([
components.$spread().( /* Сканируем все компоненты */
$ID := $keys()[0];
{ /* Генерируем массив признаков проблем */
"isComponent": *.entity = "component", /* Это компонент */
"isDocHubDomain": $boolean($match($ID, /dochub\.front.*/)), /* в домене DocHub */
"id": $ID, /* Запоминаем идентификатор компонента */
"isvalid": $validator($.*) /* Валидируем компонент по схеме */
}
)
][isDocHubDomain and isComponent and isvalid != true]).isvalid.{ /* Генерируем отклонения по выявленным нарушениям */
"uid": $.params.missingProperty & "-component-" & %.id, /* Уникальный идентификатор выявленной ошибки */
"location": "/architect/components/" & %.id, /* Ссылка на расположение объекта ошибки */
"correction": "Заполните необходимые поля", /* Рекомендации как исправить проблему */
"description": message
}])
)
```
Результат работы валидатора аналогичен простому валидатору.
[Далее](/docs/dochub.rules.exceptions)
39 changes: 32 additions & 7 deletions src/components/JSONata/DevTool.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
v-on:keydown.tab.prevent="tabber($event)" />
</split-area>
<split-area v-bind:size="70" class="area-space">
<textarea v-model="result" class="area" readonly style="background-color: #f5f5f5;" wrap="off" />
<pre v-if="error" class="area" v-html="errorExplain" />
<textarea v-else v-model="result" class="area" readonly style="background-color: #f5f5f5;" wrap="off" />
</split-area>
</Split>
</v-container>
Expand All @@ -18,33 +19,57 @@
<script>
import query from '@/manifest/query';
import cookie from 'vue-cookie';
const COOKIE_NAME = 'json-dev-tool-query';
export default {
name: 'JSONataDevTool',
data() {
return {
query: '"Здесь введите JSONata запрос."',
query: cookie.get(COOKIE_NAME) || '"Здесь введите JSONata запрос."',
result: null,
error: null,
observer: null
};
},
computed: {
errorExplain() {
if (this.error) {
const pos = this.error.position;
return `Error: ${this.error.message}\n\n${this.query.slice(0, pos)} <span style="color:red"> ${this.query.slice(pos)} </span>`;
}
return null;
}
},
watch: {
query(value) {
this.observer && clearTimeout(this.observer);
this.observer = setTimeout(() => {
this.result = JSON.stringify(query.expression(value).evaluate(this.manifest), null, 4);
this.execute();
this.observer = null;
}, 500);
cookie.set(COOKIE_NAME, value, 365);
}
},
mounted() {
this.execute();
},
methods: {
execute() {
this.error = null;
const jsonata = query.expression(this.query);
jsonata.onError = (e) => this.error = e;
this.result = JSON.stringify(jsonata.evaluate(this.manifest), null, 4);
},
tabber(event) {
if (event) {
event.preventDefault();
let startText = this.query.slice(0, event.target.selectionStart);
let endText = this.query.slice(event.target.selectionStart);
const originalSelectionStart = event.target.selectionStart;
const startText = this.query.slice(0, event.target.selectionStart);
const endText = this.query.slice(event.target.selectionStart);
this.query = `${startText}\t${endText}`;
event.target.selectionEnd = event.target.selectionStart + 1;
event.target.value = this.query;
event.target.selectionEnd = event.target.selectionStart = originalSelectionStart + 1;
}
}
}
Expand Down
24 changes: 18 additions & 6 deletions src/manifest/query.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import jsonata from 'jsonata';
import ajv from 'ajv';
const ajv_localize = require('ajv-i18n/localize/ru');

const SCHEMA_CONTEXT = `
(
Expand Down Expand Up @@ -629,7 +630,13 @@ function mergeDeep(sources) {

function jsonSchema(schema) {
const rules = new ajv({allErrors: true});
return rules.compile(schema);
const validator = rules.compile(schema);
return (data) => {
const isOk = validator(data);
if (isOk) return true;
ajv_localize(validator.errors);
return validator.errors;
};
}

export default {
Expand All @@ -639,12 +646,20 @@ export default {
expression(expression, self_) {
const obj = {
expression,
core : jsonata(expression),
core : null,
onError: null, // Событие ошибки выполнения запроса
// Исполняет запрос
// context - контекст исполнения запроса
// def - если возникла ошибка, будет возращено это значение
evaluate(context, def) {
try {
if (!this.core) {
this.core = jsonata(this.expression);
this.core.assign('self', self_);
this.core.registerFunction('wcard', wcard);
this.core.registerFunction('mergedeep', mergeDeep);
this.core.registerFunction('jsonschema', jsonSchema);
}
return Object.freeze(this.core.evaluate(context));
} catch(e) {
// eslint-disable-next-line no-console
Expand All @@ -653,14 +668,11 @@ export default {
console.log(this.expression.slice(0, e.position) + '%c' + this.expression.slice(e.position), 'color:red' );
// eslint-disable-next-line no-console
console.error(e);
this.onError && this.onError(e);
return def;
}
}
};
obj.core.assign('self', self_);
obj.core.registerFunction('wcard', wcard);
obj.core.registerFunction('mergedeep', mergeDeep);
obj.core.registerFunction('jsonschema', jsonSchema);
return obj;
},
// Меню
Expand Down
2 changes: 0 additions & 2 deletions src/storage/gitlab.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,6 @@ export default {
context.commit('setIsReloading', true);
};
parser.onError = (action, data) => {
// eslint-disable-next-line no-debugger
debugger;
const error = data.error || {};
const url = (data.error.config || {url: data.uri}).url;
const uid = '$' + crc16(url);
Expand Down

0 comments on commit ecf038d

Please sign in to comment.