Skip to content

Commit 86499e1

Browse files
authored
Merge pull request #3 from kbarbounakis/use-json-schema-converter
use json schema parser
2 parents d8dacf4 + 2daed5a commit 86499e1

16 files changed

Lines changed: 682 additions & 183 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ cmake-build-*/
215215
# IntelliJ
216216
out/
217217

218+
.tmp
219+
218220
# mpeltonen/sbt-idea plugin
219221
.idea_modules/
220222

package-lock.json

Lines changed: 92 additions & 178 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@themost/schemer",
3-
"version": "1.0.0",
3+
"version": "2.1.0",
44
"description": "A collection of utilities for working with database schemas across environments with different naming conventions.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
@@ -32,8 +32,10 @@
3232
},
3333
"dependencies": {
3434
"@themost/common": "^2.11.0",
35-
"@themost/events": "^1.3.0",
35+
"@themost/events": "^1.5.0",
3636
"@themost/query": "^2.14.5",
37-
"@themost/sqlite": "^2.8.2"
37+
"@themost/sqlite": "^2.8.2",
38+
"@types/json-schema": "^7.0.15",
39+
"lodash": "^4.17.21"
3840
}
3941
}

spec/JsonSchemaParser.spec.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { JsonSchemaParser } from "../src";
2+
import { readFile, mkdir, writeFile, stat } from 'fs/promises';
3+
import path from 'path';
4+
5+
describe('JsonSchemaParser', () => {
6+
7+
beforeAll(async () => {
8+
await stat(path.join(__dirname, '.tmp')).catch(async () => {
9+
await mkdir(path.join(__dirname, '.tmp'));
10+
});
11+
});
12+
13+
it('should create instance', () => {
14+
const parser = new JsonSchemaParser();
15+
expect(parser).toBeTruthy();
16+
});
17+
it('should parse json schema', async () => {
18+
const parser = new JsonSchemaParser();
19+
const schema = JSON.parse(await readFile(path.join(__dirname, './schemas/UserProfile.json'), 'utf-8'));
20+
const result = parser.parse(schema, {
21+
name: 'UserProfile',
22+
title: 'User Profile',
23+
description: 'A user profile'
24+
}).get('UserProfile');
25+
expect(result).toBeTruthy();
26+
await writeFile(path.join(__dirname, '.tmp/UserProfile.json'), JSON.stringify(result, null, 2));
27+
expect(result.name).toBe('UserProfile');
28+
for (const field of result.fields) {
29+
expect(field.name).toBeTruthy();
30+
expect(field.type).toBeTruthy();
31+
}
32+
const email = result.fields.find(field => field.name === 'email');
33+
expect(email).toBeTruthy();
34+
expect(email.nullable).toBe(false);
35+
});
36+
37+
it('should parse additional json schema', async () => {
38+
39+
const otherSchemas = [];
40+
const schema1 = JSON.parse(await readFile(path.join(__dirname, './schemas/UserProfile.json'), 'utf-8'))
41+
schema1.$name = 'UserProfile';
42+
otherSchemas.push(schema1);
43+
44+
const parser = new JsonSchemaParser();
45+
parser.resolvingSchema.subscribe((event) => {
46+
const schema = otherSchemas.find(s => s.$id === event.id);
47+
if (schema) {
48+
event.schema.push(schema);
49+
}
50+
});
51+
52+
const schema = JSON.parse(await readFile(path.join(__dirname, './schemas/BlogPost.json'), 'utf-8'));
53+
const results = parser.parse(schema, {
54+
name: 'BlogPost',
55+
title: 'BlogPost',
56+
description: 'A blog post'
57+
});
58+
for (const [name, result] of results) {
59+
await writeFile(path.join(__dirname, `.tmp/${name}.json`), JSON.stringify(result, null, 2));
60+
expect(result.name).toBe(name);
61+
for (const field of result.fields) {
62+
expect(field.name).toBeTruthy();
63+
expect(field.type).toBeTruthy();
64+
}
65+
}
66+
expect(results.size).toBeGreaterThan(1);
67+
});
68+
});

spec/schemas/BlogPost.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"$id": "https://example.com/blog-post.schema.json",
3+
"$schema": "https://json-schema.org/draft/2020-12/schema",
4+
"description": "A representation of a blog post",
5+
"type": "object",
6+
"required": ["title", "content", "author"],
7+
"properties": {
8+
"title": {
9+
"type": "string"
10+
},
11+
"content": {
12+
"type": "string"
13+
},
14+
"publishedDate": {
15+
"type": "string",
16+
"format": "date-time"
17+
},
18+
"author": {
19+
"$ref": "https://example.com/user-profile.schema.json"
20+
},
21+
"tags": {
22+
"type": "array",
23+
"items": {
24+
"type": "string"
25+
}
26+
}
27+
}
28+
}

spec/schemas/UserProfile.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"$id": "https://example.com/user-profile.schema.json",
3+
"$schema": "https://json-schema.org/draft/2020-12/schema",
4+
"description": "A representation of a user profile",
5+
"type": "object",
6+
"required": ["username", "email"],
7+
"properties": {
8+
"username": {
9+
"type": "string"
10+
},
11+
"email": {
12+
"type": "string",
13+
"format": "email"
14+
},
15+
"fullName": {
16+
"type": "string"
17+
},
18+
"age": {
19+
"type": "integer",
20+
"minimum": 0
21+
},
22+
"location": {
23+
"type": "string"
24+
},
25+
"interests": {
26+
"type": "array",
27+
"items": {
28+
"type": "string"
29+
}
30+
}
31+
}
32+
}

src/JsonEnumSchemaParser.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { DataModelProperties } from '@themost/common';
2+
import { JSONSchema7WithName } from './JsonSchemaParserBase';
3+
4+
export declare class JsonEnumSchemaParser {
5+
constructor();
6+
parse(schema: JSONSchema7WithName, template?: DataModelProperties): Map<string, DataModelProperties>;
7+
}

src/JsonEnumSchemaParser.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import assign from 'lodash/assign';
2+
import cloneDeep from 'lodash/cloneDeep';
3+
4+
class JsonEnumSchemaParser {
5+
constructor() {
6+
//
7+
}
8+
9+
/**
10+
* Parses a JSON schema which represents an enumeration -where properties are values of enum model- and generates a data model schema.
11+
* @param {import('json-schema').JSONSchema7} schema
12+
* @param {import('@themost/common').DataModelProperties=} template
13+
* @returns {Map<string, import('@themost/common').DataModelProperties>}
14+
*/
15+
parse(schema, template) {
16+
17+
const enumTemplate = cloneDeep(template || {});
18+
19+
/**
20+
* @type {import('@themost/common').DataModelProperties}
21+
*/
22+
const model = assign({
23+
'$schema': 'https://themost-framework.github.io/themost/models/2018/2/schema.json',
24+
'@id': schema.$id,
25+
'caching': 'conditional',
26+
'hidden': true,
27+
'name': null,
28+
'title': schema.title,
29+
'description': schema.description,
30+
'source': null,
31+
'view': null,
32+
'version': schema.$version || '1.0.0',
33+
'fields': [
34+
{
35+
'@id': 'https://universis.io/schemas/name',
36+
'name': 'name',
37+
'title': 'Name',
38+
'type': 'Text',
39+
'nullable': false,
40+
'size': 36,
41+
'primary': true
42+
},
43+
{
44+
'@id': 'https://universis.io/schemas/description',
45+
'name': 'description',
46+
'title': 'Description',
47+
'type': 'Note'
48+
}
49+
],
50+
'constraints': [],
51+
'eventListeners': [],
52+
'privileges': [
53+
{
54+
'type': 'global',
55+
'mask': 1,
56+
'account': '*'
57+
},
58+
{
59+
'type': 'global',
60+
'mask': 14,
61+
'account': 'Administrators'
62+
}
63+
]
64+
}, enumTemplate);
65+
66+
if (model.view == null && model.source == null && model.name != null) {
67+
// assign default source and view for enum models
68+
assign(model, {
69+
'source': model.name,
70+
'view': model.name
71+
});
72+
}
73+
74+
model.seed = Object.keys(schema.properties).map((property) => {
75+
return {
76+
'name': property,
77+
'description': schema.properties[property].description
78+
};
79+
});
80+
return new Map([
81+
[model.name,
82+
model]
83+
]);
84+
}
85+
}
86+
export { JsonEnumSchemaParser };

src/JsonSchemaDataTypes.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export const JsonSchemaDataTypes = new Map([
2+
[
3+
'string', 'Text'
4+
],
5+
[
6+
'integer', 'Integer'
7+
],
8+
[
9+
'float', 'Number'
10+
],
11+
[
12+
'number', 'Number'
13+
],
14+
[
15+
'boolean', 'Boolean'
16+
],
17+
[
18+
'object', 'Object'
19+
],
20+
[
21+
'date-time', 'DateTime'
22+
],
23+
[
24+
'date', 'Date'
25+
],
26+
[
27+
'url', 'Url'
28+
],
29+
[
30+
'uri', 'Url'
31+
],
32+
[
33+
'email', 'Email'
34+
],
35+
[
36+
'uuid', 'Guid'
37+
]
38+
]);

src/JsonSchemaParser.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { DataModelProperties, DataFieldBase } from '@themost/common';
2+
import { JSONSchema7, JSONSchema7Definition } from 'json-schema';
3+
import { SyncSeriesEventEmitter } from '@themost/events';
4+
import { JSONSchema7WithName, JsonSchemaParserBase } from './JsonSchemaParserBase';
5+
import { JsonSchemaParserPropertyEvent, JsonSchemaParserResolveEvent } from './JsonSchemaSubscribers';
6+
export declare class JsonSchemaParser implements JsonSchemaParserBase {
7+
resolvingProperty: SyncSeriesEventEmitter<JsonSchemaParserPropertyEvent>;
8+
resolvingSchema: SyncSeriesEventEmitter<JsonSchemaParserResolveEvent>;
9+
parse(schema: JSONSchema7): Map<string, DataModelProperties>;
10+
getDefinition(jsonSchema: JSONSchema7, referencePath: string): JSONSchema7WithName | undefined;
11+
getType(definition: JSONSchema7Definition): string | undefined;
12+
getSchema(id: string): JSONSchema7WithName | undefined;
13+
}

0 commit comments

Comments
 (0)