Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add context support #427

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions lib/metaschema-config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ const {
actionCreator,
} = require('./action');

const {
addContext,
postprocessContext,
validateContext,
} = require('./context');

const { preprocessResources, addResources } = require('./resource');
const { addApplication, processApplication } = require('./application');

Expand Down Expand Up @@ -139,6 +145,7 @@ module.exports = {
localDecorators,
pathToType: {
domains: 'domains',
context: 'context',
category: 'category',
action: 'action',
form: 'form',
Expand All @@ -151,6 +158,7 @@ module.exports = {
prepare: ms => {
ms.categories = new Map();
ms.domains = new Map();
ms.context = new Map();
ms.actions = new Map();
ms.resources = {
common: new Map(),
Expand All @@ -171,6 +179,8 @@ module.exports = {
resolve: (ms, type, name) => {
if (type === 'domains') {
return ms.domains.get(name);
} else if (type === 'context') {
return ms.context.get(name);
} else if (type === 'category') {
return ms.categories.get(name);
} else {
Expand All @@ -186,6 +196,11 @@ module.exports = {
...config.processors.domains,
validateSchema: [validateSchema],
},
context: {
add: [addContext],
postprocess: [postprocessContext],
validateInstance: validateContext,
},
category: {
add: [...config.processors.category.add, addCategory],
postprocess: [
Expand Down Expand Up @@ -222,10 +237,11 @@ module.exports = {
processOrder: {
domains: 0,
category: 1,
form: 2,
action: 3,
resource: 4,
application: 5,
context: 2,
form: 3,
action: 4,
resource: 5,
application: 6,
},
},
};
52 changes: 52 additions & 0 deletions lib/metaschema-config/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';

const { SchemaValidationError } = require('metaschema').errors;

const addContext = (context, ms) => {
const errors = [];
const isDuplicated = ms.context.size > 0;
if (isDuplicated) {
errors.push(
new SchemaValidationError('duplicate', context.name, {
type: 'context',
value: context.name,
})
);
} else {
Object.entries(context.definition).forEach(([name, field]) =>
ms.context.set(name, field)
);
}
return errors;
};

const postprocessContext = (context, ms) => {
const errors = [];
for (const [fieldName, field] of Object.entries(context.definition)) {
const domain = ms.domains.get(field.domain);
if (domain) {
field.definition = domain;
} else {
errors.push(
new SchemaValidationError(
'unresolved',
`${context.name}.${fieldName}`,
{ type: 'domain', value: field.domain }
)
);
}
}
return errors;
};

const validateContext = (ms, schema, instance, options) => {
const { domain } = schema;
const error = ms.validate('domains', domain, instance, options);
return error ? error.errors : [];
};

module.exports = {
addContext,
postprocessContext,
validateContext,
};
49 changes: 22 additions & 27 deletions lib/metaschema-config/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,14 @@ const {
extractDecorator,
} = require('metaschema');

const dataTypes = ['domain', 'category', 'context'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const dataTypes = ['domain', 'category', 'context'];
const dataTypes = Object.keys(propToTypes);

const getDefinition = (type, schema) =>
type === 'category' ? schema.definition : schema;

const processFields = (ms, category, fields, source) => {
const errors = [];
for (const [key, field] of Object.entries(fields)) {
if (field.domain) {
const def = ms.domains.get(field.domain);
if (!def) {
errors.push(
new SchemaValidationError('unresolved', `${source}.${key}`, {
type: 'domain',
value: field.domain,
})
);
} else {
Object.assign(field, { domain: field.domain, definition: def });
}
} else if (field.category) {
const cat = ms.categories.get(field.category);
if (!cat) {
errors.push(
new SchemaValidationError('unresolved', `${source}.${key}`, {
type: 'category',
value: field.category,
})
);
} else {
field.definition = cat.definition;
}
} else if (typeof field === 'string') {
if (typeof field === 'string') {
const src = `${source}.${key}`;
if (!category) {
errors.push(
Expand All @@ -44,11 +24,26 @@ const processFields = (ms, category, fields, source) => {
continue;
}
try {
const def = extractByPath(category, field.field, ms, src);
Object.assign(field, def);
Object.assign(field, extractByPath(category, field.field, ms, src));
} catch (error) {
errors.push(error);
}
} else {
for (const type of dataTypes) {
const value = field[type];
if (value) {
const path = type === 'domain' ? 'domains' : type;
const def = ms[path].get(value);
if (!def) {
const info = { type, value };
errors.push(
new SchemaValidationError('unresolved', `${source}.${key}`, info)
);
} else {
field.definition = getDefinition(type, def);
}
}
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions lib/metaschema-config/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ const validate = (ms, schema, instance, options = {}) => {
if (property.domain) {
const error = ms.validate('domains', property.domain, value, opts);
if (error) errors.push(...error.errors);
} else if (property.context) {
const error = ms.validate('context', property.context, value, opts);
if (error) errors.push(...error.errors);
} else if (property.category) {
errors.push(...validateLink(ms, property, value, opts));
}
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/context/duplicate/context.context
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
({
Nomen: { domain: 'Nomen' },
});
3 changes: 3 additions & 0 deletions test/fixtures/context/duplicate/custom.domains
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
({
Nomen: { type: 'string' },
});
3 changes: 3 additions & 0 deletions test/fixtures/context/duplicate/duplicateDomain.context
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
({
DuplicateNomen: { domain: 'Nomen' },
});
3 changes: 3 additions & 0 deletions test/fixtures/context/unresolved/context.context
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
({
Nomen: { domain: 'Nomen' },
});
6 changes: 6 additions & 0 deletions test/fixtures/context/valid/PublicAction.action
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Action({
Public: true,
Args: {
valueFromContext: { context: 'NomenFromContext' },
},
});
3 changes: 3 additions & 0 deletions test/fixtures/context/valid/context.context
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
({
NomenFromContext: { domain: 'Nomen' },
});
3 changes: 3 additions & 0 deletions test/fixtures/context/valid/custom.domains
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
({
Nomen: { type: 'string' },
});
58 changes: 58 additions & 0 deletions test/ms.context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

const metaschema = require('metaschema');
const metatests = require('metatests');

const { options, config } = require('../lib/metaschema-config/config');

const {
MetaschemaError,
SchemaValidationError,
ValidationError,
} = metaschema.errors;

metatests.test('metaschema correct context usage', async test => {
const schema = await metaschema.fs.load(
'test/fixtures/context/valid',
options,
config
);

test.assertNot(
schema.validate('action', 'PublicAction', { valueFromContext: 'value' })
);

test.isError(
schema.validate('action', 'PublicAction', { valueFromContext: 10 }),
new MetaschemaError([
new ValidationError('invalidType', 'valueFromContext', {
expected: 'string',
actual: 'number',
}),
])
);
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a positive test, to ensure that it is possible to use context args in actions.

metatests.test('metaschema context error on duplicated domains', async test => {
await test.rejects(
metaschema.fs.load('test/fixtures/context/duplicate', options, config),
new MetaschemaError([
new SchemaValidationError('duplicate', 'duplicateDomain', {
type: 'context',
value: 'duplicateDomain',
}),
])
);
});

metatests.test('metaschema context error on unresolved domain', async test => {
await test.rejects(
metaschema.fs.load('test/fixtures/context/unresolved', options, config),
new MetaschemaError([
new SchemaValidationError('unresolved', 'context.Nomen', {
type: 'domain',
value: 'Nomen',
}),
])
);
});