Skip to content

Commit bc0dd65

Browse files
Merge pull request #14 from Exabyte-io/featute/SOF-6144
SOF-6144: JSON Schemas features for InMemoryEntity
2 parents 76b2b13 + 7bb23fd commit bc0dd65

File tree

8 files changed

+493
-4
lines changed

8 files changed

+493
-4
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ node_modules/
44
.eslintcache
55
.nyc_output/
66
.idea/
7-
7+
*.DS_Store

package-lock.json

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

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@
4848
"@babel/preset-react": "7.16.7",
4949
"@babel/register": "^7.16.0",
5050
"@babel/runtime-corejs3": "7.16.8",
51+
"@exabyte-io/esse.js": "2022.7.28-1",
5152
"crypto-js": "^4.1.1",
53+
"json-schema-merge-allof": "^0.8.1",
5254
"lodash": "^4.17.21",
5355
"mathjs": "^3.9.0",
5456
"mixwith": "^0.1.1",

src/JSONSchemasInterface.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { schemas } from "@exabyte-io/esse.js/schemas";
2+
import mergeAllOf from "json-schema-merge-allof";
3+
4+
const schemasCache = new Map();
5+
export class JSONSchemasInterface {
6+
/**
7+
*
8+
* @param {string} schemaId id of JSON schema from ESSE
9+
* @returns {Object.<string, any>} resolved JSON schema
10+
*/
11+
static schemaById(schemaId) {
12+
if (!schemasCache.has(schemaId)) {
13+
const originalSchema = schemas.find((schema) => schema.schemaId === schemaId);
14+
15+
const schema = mergeAllOf(originalSchema, {
16+
resolvers: {
17+
defaultResolver: mergeAllOf.options.resolvers.title,
18+
},
19+
});
20+
21+
schemasCache.set(schemaId, schema);
22+
}
23+
24+
return schemasCache.get(schemaId);
25+
}
26+
27+
/**
28+
* @example <caption>Search by schemaId regex</caption>
29+
* JSONSchemasInterface.matchSchema({
30+
* schemaId: {
31+
* $regex: 'software-application'
32+
* }
33+
* })
34+
*
35+
* @example <caption>Search by schemaId and title regex</caption>
36+
* JSONSchemasInterface.matchSchema({
37+
* schemaId: {
38+
* $regex: 'software-application'
39+
* },
40+
* title: {
41+
* $regex: 'application'
42+
* }
43+
* })
44+
*
45+
* @param {Object} query - An object containing mongo-like search query
46+
* @returns {Object|null} JSON schema
47+
*/
48+
static matchSchema(query) {
49+
const searchFields = Object.keys(query);
50+
return schemas.find((schema) => {
51+
return searchFields.every((field) => {
52+
const { $regex } = query[field];
53+
return new RegExp($regex).test(schema[field]);
54+
});
55+
});
56+
}
57+
}

src/entity/in_memory.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import mergeAllOf from "json-schema-merge-allof";
12
import lodash from "lodash";
23

34
// import { ESSE } from "@exabyte-io/esse.js";
45
import { deepClone } from "../utils/clone";
6+
import { getMixSchemasByClassName, getSchemaByClassName } from "../utils/schemas";
57

68
// TODO: https://exabyte.atlassian.net/browse/SOF-5946
79
// const schemas = new ESSE().schemas;
@@ -171,4 +173,62 @@ export class InMemoryEntity {
171173
}
172174
return filtered[0];
173175
}
176+
177+
/**
178+
* @summary If there any nested in-memory entities, first resolve them
179+
* and then mix with original schema in baseJSONSchema()
180+
* @returns {Object.<string,InMemoryEntity>|null}
181+
* @example
182+
* class Workflow extends InMemoryEntity {
183+
* get customJsonSchemaProperties() {
184+
* return {
185+
* subworkflows: {
186+
* type: 'array',
187+
* items: Subworkflow.jsonSchema
188+
* }
189+
* };
190+
* }
191+
* }
192+
*/
193+
static get customJsonSchemaProperties() {
194+
return null;
195+
}
196+
197+
/**
198+
* Returns original ESSE schema with nested properties from customJsonSchemaProperties
199+
* @see customJsonSchemaProperties
200+
* @returns {Object} schema
201+
*/
202+
static get baseJSONSchema() {
203+
if (!this.customJsonSchemaProperties) {
204+
return getSchemaByClassName(this.name);
205+
}
206+
207+
const { properties, ...schema } = getSchemaByClassName(this.name);
208+
209+
return {
210+
...schema,
211+
properties: {
212+
...properties,
213+
...this.customJsonSchemaProperties,
214+
},
215+
};
216+
}
217+
218+
/**
219+
* Returns resolved JSON schema with custom properties and all mixes from schemas.js
220+
* @returns {Object} schema
221+
*/
222+
static get jsonSchema() {
223+
return mergeAllOf(
224+
{
225+
allOf: [this.baseJSONSchema, ...getMixSchemasByClassName(this.name)],
226+
},
227+
{
228+
resolvers: {
229+
defaultResolver: mergeAllOf.options.resolvers.title,
230+
},
231+
},
232+
);
233+
}
174234
}

src/utils/schemas.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { JSONSchemasInterface } from "../JSONSchemasInterface";
2+
3+
export const baseSchemas = {
4+
Material: "material",
5+
Entity: "system-entity",
6+
BankMaterial: "material",
7+
Workflow: "workflow",
8+
Subworkflow: "workflow-subworkflow",
9+
BankWorkflow: "workflow",
10+
Job: "job",
11+
Application: "software-application",
12+
Executable: "software-executable",
13+
Flavor: "software-flavor",
14+
Template: "software-template",
15+
AssertionUnit: "workflow-unit-assertion",
16+
AssignmentUnit: "workflow-unit-assignment",
17+
ConditionUnit: "workflow-unit-condition",
18+
ExecutionUnit: "workflow-unit-execution",
19+
IOUnit: "workflow-unit-io",
20+
MapUnit: "workflow-unit-map",
21+
ProcessingUnit: "workflow-unit-processing",
22+
ReduceUnit: "workflow-unit-reduce",
23+
SubworkflowUnit: "workflow-unit",
24+
Unit: "workflow-unit",
25+
};
26+
27+
const entityMix = [
28+
"system-description-object",
29+
"system-base-entity-set",
30+
"system-sharing",
31+
"system-metadata",
32+
"system-defaultable",
33+
];
34+
35+
const subWorkflowMix = ["system-system-name", "system-is-multi-material"];
36+
37+
const workflowMix = ["workflow-base-flow", "system-history", "system-is-outdated"];
38+
39+
const bankMaterialMix = ["material-conventional", "system-creator-account"];
40+
41+
const bankWorkflowMix = ["system-creator-account"];
42+
43+
const jobMix = ["system-status", "system-job-extended"];
44+
45+
const unitMix = ["system-unit-extended", "system-status", "workflow-unit-runtime-runtime-items"];
46+
47+
const assignmentUnitMix = ["system-scope"];
48+
49+
const flavorMix = ["system-is-multi-material"];
50+
51+
const systemEntityMix = ["system-entity"];
52+
53+
export const mixSchemas = {
54+
Entity: [...entityMix],
55+
Material: [...entityMix],
56+
BankMaterial: [...entityMix, ...bankMaterialMix],
57+
Workflow: [...entityMix, ...subWorkflowMix, ...workflowMix],
58+
Subworkflow: [...subWorkflowMix],
59+
BankWorkflow: [...entityMix, ...subWorkflowMix, ...workflowMix, ...bankWorkflowMix],
60+
Job: [...entityMix, ...jobMix],
61+
Application: [...entityMix, ...systemEntityMix],
62+
Executable: [...entityMix, ...systemEntityMix],
63+
Flavor: [...entityMix, ...flavorMix, ...systemEntityMix],
64+
Template: [...entityMix, ...systemEntityMix],
65+
AssertionUnit: [...unitMix],
66+
AssignmentUnit: [...unitMix, ...assignmentUnitMix],
67+
ConditionUnit: [...unitMix],
68+
ExecutionUnit: [...unitMix],
69+
IOUnit: [...unitMix],
70+
MapUnit: [...unitMix],
71+
ProcessingUnit: [...unitMix],
72+
ReduceUnit: [...unitMix],
73+
SubworkflowUnit: [...unitMix],
74+
Unit: [...unitMix],
75+
};
76+
77+
export function getSchemaByClassName(className) {
78+
return baseSchemas[className] ? JSONSchemasInterface.schemaById(baseSchemas[className]) : null;
79+
}
80+
81+
export function getMixSchemasByClassName(className) {
82+
return mixSchemas[className]
83+
? mixSchemas[className].map((schemaId) => JSONSchemasInterface.schemaById(schemaId))
84+
: [];
85+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { expect } from "chai";
2+
3+
import { JSONSchemasInterface } from "../src/JSONSchemasInterface";
4+
import { baseSchemas, mixSchemas } from "../src/utils/schemas";
5+
6+
describe("JSONSchemasInterface", () => {
7+
it("can find main schema", () => {
8+
Object.values(baseSchemas).forEach((schemaId) => {
9+
const schema = JSONSchemasInterface.schemaById(schemaId);
10+
expect(schema).to.be.an("object");
11+
});
12+
});
13+
14+
it("can find mix schemas", () => {
15+
Object.values(mixSchemas).forEach((schemaIds) => {
16+
schemaIds.forEach((schemaId) => {
17+
const schema = JSONSchemasInterface.schemaById(schemaId);
18+
expect(schema).to.be.an("object");
19+
});
20+
});
21+
});
22+
23+
it("can match schemas", () => {
24+
const schemaId = Object.values(baseSchemas)[0];
25+
const schema = JSONSchemasInterface.matchSchema({
26+
schemaId: {
27+
$regex: schemaId,
28+
},
29+
});
30+
31+
expect(schema).to.be.an("object");
32+
});
33+
});

tests/in_memory.tests.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,19 @@ describe("InMemoryEntity", () => {
4545
const entity = new InMemoryEntity(obj);
4646
expect(JSON.stringify(entity.toJSON())).to.be.equal(JSON.stringify(obj));
4747
});
48+
49+
it("jsonSchema returns correct schema", () => {
50+
class Entity extends InMemoryEntity {
51+
static get customJsonSchemaProperties() {
52+
return {
53+
nested: {
54+
type: "string",
55+
},
56+
};
57+
}
58+
}
59+
expect(Entity.jsonSchema).to.be.an("object");
60+
expect(Entity.jsonSchema).to.have.nested.property("properties.isDefault"); // check mix schemas
61+
expect(Entity.jsonSchema).to.have.nested.property("properties.nested.type"); // check custom properties
62+
});
4863
});

0 commit comments

Comments
 (0)