From a09ffacff91cd93140c94f43e5664231595a8d48 Mon Sep 17 00:00:00 2001 From: Dmytro Nechai Date: Fri, 26 Apr 2019 16:54:07 +0300 Subject: [PATCH] Add support for context --- lib/metaschema-config/config.js | 24 ++++++-- lib/metaschema-config/context.js | 52 +++++++++++++++++ lib/metaschema-config/utils.js | 49 +++++++--------- lib/metaschema-config/validate.js | 3 + .../context/duplicate/context.context | 3 + .../fixtures/context/duplicate/custom.domains | 3 + .../context/duplicate/duplicateDomain.context | 3 + .../context/unresolved/context.context | 3 + .../context/valid/PublicAction.action | 6 ++ test/fixtures/context/valid/context.context | 3 + test/fixtures/context/valid/custom.domains | 3 + test/ms.context.js | 58 +++++++++++++++++++ 12 files changed, 179 insertions(+), 31 deletions(-) create mode 100644 lib/metaschema-config/context.js create mode 100644 test/fixtures/context/duplicate/context.context create mode 100644 test/fixtures/context/duplicate/custom.domains create mode 100644 test/fixtures/context/duplicate/duplicateDomain.context create mode 100644 test/fixtures/context/unresolved/context.context create mode 100644 test/fixtures/context/valid/PublicAction.action create mode 100644 test/fixtures/context/valid/context.context create mode 100644 test/fixtures/context/valid/custom.domains create mode 100644 test/ms.context.js diff --git a/lib/metaschema-config/config.js b/lib/metaschema-config/config.js index a0057828..79cf6c05 100644 --- a/lib/metaschema-config/config.js +++ b/lib/metaschema-config/config.js @@ -30,6 +30,12 @@ const { actionCreator, } = require('./action'); +const { + addContext, + postprocessContext, + validateContext, +} = require('./context'); + const { preprocessResources, addResources } = require('./resource'); const { addApplication, processApplication } = require('./application'); @@ -139,6 +145,7 @@ module.exports = { localDecorators, pathToType: { domains: 'domains', + context: 'context', category: 'category', action: 'action', form: 'form', @@ -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(), @@ -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 { @@ -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: [ @@ -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, }, }, }; diff --git a/lib/metaschema-config/context.js b/lib/metaschema-config/context.js new file mode 100644 index 00000000..c55e975e --- /dev/null +++ b/lib/metaschema-config/context.js @@ -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, +}; diff --git a/lib/metaschema-config/utils.js b/lib/metaschema-config/utils.js index 803a3dec..851217bc 100644 --- a/lib/metaschema-config/utils.js +++ b/lib/metaschema-config/utils.js @@ -6,34 +6,14 @@ const { extractDecorator, } = require('metaschema'); +const dataTypes = ['domain', 'category', 'context']; +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( @@ -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); + } + } + } } } diff --git a/lib/metaschema-config/validate.js b/lib/metaschema-config/validate.js index 1fc87f80..5810f809 100644 --- a/lib/metaschema-config/validate.js +++ b/lib/metaschema-config/validate.js @@ -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)); } diff --git a/test/fixtures/context/duplicate/context.context b/test/fixtures/context/duplicate/context.context new file mode 100644 index 00000000..02e94e61 --- /dev/null +++ b/test/fixtures/context/duplicate/context.context @@ -0,0 +1,3 @@ +({ + Nomen: { domain: 'Nomen' }, +}); diff --git a/test/fixtures/context/duplicate/custom.domains b/test/fixtures/context/duplicate/custom.domains new file mode 100644 index 00000000..a16a5cf8 --- /dev/null +++ b/test/fixtures/context/duplicate/custom.domains @@ -0,0 +1,3 @@ +({ + Nomen: { type: 'string' }, +}); diff --git a/test/fixtures/context/duplicate/duplicateDomain.context b/test/fixtures/context/duplicate/duplicateDomain.context new file mode 100644 index 00000000..ca0433ca --- /dev/null +++ b/test/fixtures/context/duplicate/duplicateDomain.context @@ -0,0 +1,3 @@ +({ + DuplicateNomen: { domain: 'Nomen' }, +}); diff --git a/test/fixtures/context/unresolved/context.context b/test/fixtures/context/unresolved/context.context new file mode 100644 index 00000000..02e94e61 --- /dev/null +++ b/test/fixtures/context/unresolved/context.context @@ -0,0 +1,3 @@ +({ + Nomen: { domain: 'Nomen' }, +}); diff --git a/test/fixtures/context/valid/PublicAction.action b/test/fixtures/context/valid/PublicAction.action new file mode 100644 index 00000000..b1ad8c0d --- /dev/null +++ b/test/fixtures/context/valid/PublicAction.action @@ -0,0 +1,6 @@ +Action({ + Public: true, + Args: { + valueFromContext: { context: 'NomenFromContext' }, + }, +}); diff --git a/test/fixtures/context/valid/context.context b/test/fixtures/context/valid/context.context new file mode 100644 index 00000000..ca328ec1 --- /dev/null +++ b/test/fixtures/context/valid/context.context @@ -0,0 +1,3 @@ +({ + NomenFromContext: { domain: 'Nomen' }, +}); diff --git a/test/fixtures/context/valid/custom.domains b/test/fixtures/context/valid/custom.domains new file mode 100644 index 00000000..a16a5cf8 --- /dev/null +++ b/test/fixtures/context/valid/custom.domains @@ -0,0 +1,3 @@ +({ + Nomen: { type: 'string' }, +}); diff --git a/test/ms.context.js b/test/ms.context.js new file mode 100644 index 00000000..a48d5a6c --- /dev/null +++ b/test/ms.context.js @@ -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', + }), + ]) + ); +}); + +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', + }), + ]) + ); +});