From e52ef87713b5ab7092bbd27e088fe6f52a40b512 Mon Sep 17 00:00:00 2001 From: Jeremy LaCivita Date: Wed, 12 Jun 2024 13:15:08 -0400 Subject: [PATCH] fix: Validatilon and Transforms --- .../templates/codeblocks/subscriber.js | 2 +- .../templates/codeblocks/transform.mjs | 3 + .../templates/methods/calls-metrics.js | 4 +- .../javascript/templates/methods/default.js | 7 +- .../templates/methods/polymorphic-reducer.js | 6 +- .../templates/methods/temporal-set.js | 4 +- src/firebolt-openrpc.json | 6 + src/macrofier/engine.mjs | 5 +- src/openrpc/index.mjs | 7 +- src/shared/methods.mjs | 2 + src/shared/modules.mjs | 132 +++++------ src/validate/index.mjs | 205 ++++++++---------- src/validate/validator/index.mjs | 41 ++-- 13 files changed, 201 insertions(+), 223 deletions(-) create mode 100644 languages/javascript/templates/codeblocks/transform.mjs diff --git a/languages/javascript/templates/codeblocks/subscriber.js b/languages/javascript/templates/codeblocks/subscriber.js index e8071187..ad4f8509 100644 --- a/languages/javascript/templates/codeblocks/subscriber.js +++ b/languages/javascript/templates/codeblocks/subscriber.js @@ -2,4 +2,4 @@ * Subscriber: ${method.summary} * */ -function ${method.alternative}(subscriber: (${method.result.name}: ${method.result.type}) => void): Promise +function ${method.alternative}(subscriber: (${event.result.name}: ${event.result.type}) => void): Promise diff --git a/languages/javascript/templates/codeblocks/transform.mjs b/languages/javascript/templates/codeblocks/transform.mjs new file mode 100644 index 00000000..e2248d46 --- /dev/null +++ b/languages/javascript/templates/codeblocks/transform.mjs @@ -0,0 +1,3 @@ +.then( result => { + return Results.transform(result, ${transforms}) +}) \ No newline at end of file diff --git a/languages/javascript/templates/methods/calls-metrics.js b/languages/javascript/templates/methods/calls-metrics.js index b7076fb8..2fa8a48b 100644 --- a/languages/javascript/templates/methods/calls-metrics.js +++ b/languages/javascript/templates/methods/calls-metrics.js @@ -2,9 +2,7 @@ import { ${method.name} as log${method.Name} } from '../Metrics/index.mjs' function ${method.name}(${method.params.list}) { - const transforms = ${method.transforms} - - const p = Gateway.request('${info.title}.${method.name}', { ${method.params.list} }, transforms) + const p = Gateway.request('${info.title}.${method.name}', { ${method.params.list} })${method.transform} p.then(_ => { setTimeout(_ => { diff --git a/languages/javascript/templates/methods/default.js b/languages/javascript/templates/methods/default.js index 7b69f32b..1a8f6aea 100644 --- a/languages/javascript/templates/methods/default.js +++ b/languages/javascript/templates/methods/default.js @@ -1,9 +1,4 @@ function ${method.name}(${method.params.list}) { - - const transforms = ${method.transforms} - - return Gateway.request('${info.title}.${method.name}', { ${method.params.list} }).then( (result) => { - return Results.transform(result, transforms) - }) + return Gateway.request('${info.title}.${method.name}', { ${method.params.list} }) } \ No newline at end of file diff --git a/languages/javascript/templates/methods/polymorphic-reducer.js b/languages/javascript/templates/methods/polymorphic-reducer.js index ea88a474..4789f264 100644 --- a/languages/javascript/templates/methods/polymorphic-reducer.js +++ b/languages/javascript/templates/methods/polymorphic-reducer.js @@ -1,11 +1,9 @@ function ${method.name}(${method.params.list}) { - const transforms = ${method.transforms} - if (arguments.length === 1 && Array.isArray(arguments[0])) { - return Gateway.request('${info.title}.${method.name}', arguments[0], transforms) + return Gateway.request('${info.title}.${method.name}', arguments[0])${method.transform} } else { - return Gateway.request('${info.title}.${method.name}', { ${method.params.list} }, transforms) + return Gateway.request('${info.title}.${method.name}', { ${method.params.list} })${method.transform} } } \ No newline at end of file diff --git a/languages/javascript/templates/methods/temporal-set.js b/languages/javascript/templates/methods/temporal-set.js index ab456d4a..c17dc032 100644 --- a/languages/javascript/templates/methods/temporal-set.js +++ b/languages/javascript/templates/methods/temporal-set.js @@ -10,8 +10,6 @@ function ${method.name}(...args) { else if (typeof args[args.length-1] === 'number') { timeout = args.pop() } - - const transforms = ${method.transforms} - return TemporalSet.start('${info.title}', '${method.name}', '${method.temporalset.add}', '${method.temporalset.remove}', args, add, remove, timeout, transforms) + return TemporalSet.start('${info.title}', '${method.name}', '${method.temporalset.add}', '${method.temporalset.remove}', args, add, remove, timeout) } \ No newline at end of file diff --git a/src/firebolt-openrpc.json b/src/firebolt-openrpc.json index 0de28991..bce6c20c 100644 --- a/src/firebolt-openrpc.json +++ b/src/firebolt-openrpc.json @@ -1010,6 +1010,12 @@ }, "x-provided-by": { "type": "string" + }, + "x-requestor": { + "type": "string" + }, + "x-push": { + "type": "boolean" } }, "if": { diff --git a/src/macrofier/engine.mjs b/src/macrofier/engine.mjs index f1542c50..268737ae 100644 --- a/src/macrofier/engine.mjs +++ b/src/macrofier/engine.mjs @@ -1298,6 +1298,7 @@ function insertMethodMacros(template, methodObj, server, client, templates, type name: methodObj.name.split('.').pop(), params: methodObj.params.map(p => p.name).join(', '), transforms: null, + transform: '', alternative: null, deprecated: isDeprecatedMethod(methodObj), context: [] @@ -1320,6 +1321,7 @@ function insertMethodMacros(template, methodObj, server, client, templates, type method.transforms = { methods: getMethodAttributes(flattenedMethod) } + method.transform = getTemplate('/codeblocks/transform', templates).replace(/\$\{transforms\}/g, JSON.stringify(method.transforms)) } const paramDelimiter = config.operators ? config.operators.paramDelimiter : '' @@ -1370,7 +1372,6 @@ function insertMethodMacros(template, methodObj, server, client, templates, type const setterFor = methodObj.tags.find(t => t.name === 'setter') && methodObj.tags.find(t => t.name === 'setter')['x-setter-for'] || '' const pullsResult = (puller || pullsFor) ? localizeDependencies(pullsFor || methodObj, server).params.findLast(x=>true).schema : null const pullsParams = (puller || pullsFor) ? localizeDependencies(getPayloadFromEvent(puller || methodObj, document), document, null, { mergeAllOfs: true }).properties.parameters : null - const pullsResultType = (pullsResult && (type === 'methods')) ? Types.getSchemaShape(pullsResult, server, { templateDir: state.typeTemplateDir, namespace: !config.copySchemasIntoModules }) : '' const pullsForType = pullsResult && Types.getSchemaType(pullsResult, server, { templateDir: state.typeTemplateDir, namespace: !config.copySchemasIntoModules }) const pullsParamsType = (pullsParams && (type === 'methods')) ? Types.getSchemaShape(pullsParams, server, { templateDir: state.typeTemplateDir, namespace: !config.copySchemasIntoModules }) : '' @@ -1512,6 +1513,7 @@ function insertMethodMacros(template, methodObj, server, client, templates, type .replace(/\$\{method\.property\.readonly\}/g, !getSetterFor(methodObj.name, server)) .replace(/\$\{method\.temporalset\.add\}/g, temporalAddName) .replace(/\$\{method\.temporalset\.remove\}/g, temporalRemoveName) + .replace(/\$\{method\.transform}/g, method.transform) .replace(/\$\{method\.transforms}/g, JSON.stringify(method.transforms)) .replace(/\$\{method\.seeAlso\}/g, seeAlso) .replace(/\$\{method\.item\}/g, itemName) @@ -1523,6 +1525,7 @@ function insertMethodMacros(template, methodObj, server, client, templates, type .replace(/\$\{method\.result\.type\}/g, Types.getSchemaType(result.schema, server, { templateDir: state.typeTemplateDir, title: true, asPath: false, result: true, namespace: !config.copySchemasIntoModules })) //, baseUrl: options.baseUrl .replace(/\$\{method\.result\.json\}/g, Types.getSchemaType(result.schema, server, { templateDir: 'json-types', title: true, code: false, link: false, asPath: false, expandEnums: false, namespace: !config.copySchemasIntoModules })) .replace(/\$\{event\.result\.type\}/g, isEventMethod(methodObj) ? Types.getSchemaType(event.result.schema, document, { templateDir: state.typeTemplateDir, title: true, asPath: false, result: true, namespace: !config.copySchemasIntoModules }) : '') + .replace(/\$\{event\.result\.name\}/g, isEventMethod(methodObj) ? event.result.name : '') .replace(/\$\{event\.result\.json\.type\}/g, resultJsonType) .replace(/\$\{event\.result\.json\.type\}/g, callbackResultJsonType) .replace(/\$\{event\.pulls\.param\.name\}/g, pullsEventParamName) diff --git a/src/openrpc/index.mjs b/src/openrpc/index.mjs index 42c9db88..94e5f5da 100644 --- a/src/openrpc/index.mjs +++ b/src/openrpc/index.mjs @@ -28,6 +28,9 @@ const run = async ({ server: server, template: template, schemas: schemas, + argv: { + remain: moduleWhitelist + } }) => { let serverOpenRPC = await readJson(template) @@ -66,9 +69,7 @@ const run = async ({ const isClientAPI = method => client && (isNotifier(method) || isProvider(method)) const isServerAPI = method => !isClientAPI(method) - Object.keys(modules).forEach(key => { - let json = JSON.parse(modules[key]) - + Object.values(modules).map(JSON.parse).filter(m => moduleWhitelist.length ? moduleWhitelist.includes(m.info.title) : true).forEach(json => { // pull in external markdown files for descriptions json = addExternalMarkdown(json, markdown) diff --git a/src/shared/methods.mjs b/src/shared/methods.mjs index 08cd5d10..808d2812 100644 --- a/src/shared/methods.mjs +++ b/src/shared/methods.mjs @@ -32,6 +32,8 @@ export const rename = (method, renamer) => method.name.split('.').map((x, i, arr export const getNotifier = (method, client) => client.methods.find(m => m.name === method.tags.find(t => t.name === "event")['x-notifier']) export const getEvent = (method, server) => server.methods.find(m => m.name === method.tags.find(t => t.name === "notifier")['x-event']) +export const getCapability = (method) => Object.values(capabilities(method)).map(v => Array.isArray(v) ? v.shift() : v).filter(v => typeof v === 'string').find(x => (x.startsWith('xrn:firebolt:capability'))) +export const getRole = (method) => Object.keys(capabilities(method)).find(x => ['x-uses', 'x-provides', 'x-manages'].includes(x)) export const provides = (method) => extension(method, 'x-provides') diff --git a/src/shared/modules.mjs b/src/shared/modules.mjs index 7b1f1e68..1adf7316 100644 --- a/src/shared/modules.mjs +++ b/src/shared/modules.mjs @@ -31,7 +31,7 @@ import predicates from 'crocks/predicates/index.js' import { getExternalSchemaPaths, isDefinitionReferencedBySchema, isNull, localizeDependencies, isSchema, getLocalSchemaPaths, replaceRef, getPropertySchema, getLinkedSchemaUris, getAllValuesForName, replaceUri } from './json-schema.mjs' import { getReferencedSchema } from './json-schema.mjs' const { isObject, isArray, propEq, pathSatisfies, hasProp, propSatisfies } = predicates -import { extension, getNotifier, name as methodName, rename as methodRename, provides } from './methods.mjs' +import { extension, getNotifier, isEvent, isNotifier, isPusher, name as methodName, rename as methodRename, provides } from './methods.mjs' // TODO remove these when major/rpc branch is merged const name = method => method.name.split('.').pop() @@ -98,7 +98,11 @@ const getProvidedCapabilities = (json) => { return Array.from(new Set([...getMethods(json).filter(isProviderInterfaceMethod).map(method => method.tags.find(tag => tag['x-provides'])['x-provides'])])) } const getProvidedInterfaces = (json) => { +// return json.methods?.filter(m => extension(m, 'x-interface')).map(m => extension(m, 'x-interface')) || [] + return getInterfaces(json) + const list = Array.from(new Set((json.methods || []).filter(m => m.tags.find(t => t['x-provides'])) + .filter(m => !extension(m, 'x-push')) .filter(m => !m.tags.find(t => t.name.startsWith('polymorphic-pull'))) .map(m => m.name.split('.')[0]))) @@ -109,6 +113,7 @@ const getInterfaces = (json) => { const list = Array.from(new Set((json.methods || []).filter(m => m.tags.find(t => t['x-provides'])) .filter(m => !m.tags.find(t => t.name.startsWith('registration'))) .filter(m => !m.tags.find(t => t.name.startsWith('polymorphic-pull'))) + .filter(m => !extension(m, 'x-push')) .map(m => m.name.split('.')[0]))) return list @@ -370,7 +375,7 @@ const getPayloadFromEvent = (event, client) => { } } catch (error) { - console.dir(event) + m(event) throw error } } @@ -467,11 +472,11 @@ const createEventResultSchemaFromProperty = (property, type='Changed') => { } } -const createNotifierFromProperty = property => { +const createNotifierFromProperty = (property, type='Changed') => { const subscriberType = property.tags.map(t => t['x-subscriber-type']).find(t => typeof t === 'string') || 'context' const notifier = JSON.parse(JSON.stringify(property)) - notifier.name = methodRename(notifier, name => name + 'Changed') + notifier.name = methodRename(notifier, name => name + type) Object.assign(notifier.tags.find(t => t.name.startsWith('property')), { name: 'notifier', @@ -479,13 +484,38 @@ const createNotifierFromProperty = property => { 'x-event': methodRename(notifier, name => 'on' + name.charAt(0).toUpperCase() + name.substring(1)) }) - notifier.params.push(notifier.result) - delete notifier.result + if (subscriberType === 'global') { + notifier.params = [ + { + name: "info", + schema: { + "$ref": "#/components/schemas/" + methodRename(notifier, name => name.charAt(0).toUpperCase() + name.substr(1) + 'Info') + } + } + ] + } + else { + notifier.params.push(notifier.result) + } - notifier.examples.forEach(example => { - example.params.push(example.result) - delete example.result - }) + delete notifier.result + + if (subscriberType === 'global') { + notifier.examples = property.examples.map(example => ({ + params: [ + { + name: "info", + value: Object.assign(Object.fromEntries(example.params.map(p => [p.name, p.value])), Object.fromEntries([[example.result.name, example.result.value]])) + } + ] + })) + } + else { + notifier.examples.forEach(example => { + example.params.push(example.result) + delete example.result + }) + } return notifier } @@ -573,65 +603,19 @@ const createPullEventFromPush = (pusher, json) => { return event } -const createPullProvider = (requestor, params) => { - const event = eventDefaults(JSON.parse(JSON.stringify(requestor))) - event.name = requestor.tags.find(t => t['x-provided-by'])['x-provided-by'] +const createPullProvider = (requestor) => { + const provider = JSON.parse(JSON.stringify(requestor)) + provider.name = requestor.tags.find(t => t['x-provided-by'])['x-provided-by'] const old_tags = JSON.parse(JSON.stringify(requestor.tags)) - const value = event.result - - event.tags[0]['x-response'] = value.schema - event.tags[0]['x-response'].examples = event.examples.map(e => e.result.value) - - event.result = { - "name": "request", - "schema": { - "type": "object", - "required": ["correlationId", "parameters"], - "properties":{ - "correlationId": { - "type": "string", - }, - "parameters": { - "$ref": "#/components/schemas/" + params - } - }, - "additionalProperties": false - } - } - - event.params = [] - - event.examples = event.examples.map(example => { - example.result = { - "name": "request", - "value": { - "correlationId": "xyz", - "parameters": {} - } - } - example.params.forEach(p => { - example.result.value.parameters[p.name] = p.value - }) - example.params = [] - return example - }) - - old_tags.forEach(t => { - if (t.name !== 'push-pull') - { - event.tags.push(t) - } - }) - - const caps = event.tags.find(t => t.name === 'capabilities') + const caps = provider.tags.find(t => t.name === 'capabilities') caps['x-provides'] = caps['x-uses'].pop() || caps['x-manages'].pop() caps['x-requestor'] = requestor.name delete caps['x-uses'] delete caps['x-manages'] delete caps['x-provided-by'] - return event + return provider } const createPullProviderParams = (requestor) => { @@ -1038,21 +1022,13 @@ const generatePolymorphicPullEvents = json => { } const generateProvidedByMethods = json => { - const requestors = json.methods.filter(m => !m.tags.find(t => t.name === 'event')).filter( m => m.tags && m.tags.find( t => t['x-provided-by'])) || [] - const events = json.methods .filter(m => m.tags.find(t => t.name === 'event')) - .filter( m => m.tags && m.tags.find( t => t['x-provided-by'])) - .filter(e => !json.methods.find(m => m.name === e.tags.find(t => t['x-provided-by'])['x-provided-by'])) - - const pushers = events.map(m => createNotifierFromEvent(m, json)) - pushers.forEach(m => json.methods.push(m)) + const requestors = json.methods.filter(m => !m.tags.find(t => t.name === 'notifier')).filter( m => m.tags && m.tags.find( t => t['x-provided-by'])) || [] requestors.forEach(requestor => { - const schema = createPullProviderParams(requestor) - json.methods.push(createPullProvider(requestor, schema.title)) - - json.components = json.components || {} - json.components.schemas = json.components.schemas || {} - json.components.schemas[schema.title] = schema + const provider = json.methods.find(m => (m.name === extension(requestor, 'x-provided-by')) && provides(m) && !isEvent(m) && !isPusher(m) && !isNotifier(m)) + if (!provider) { + json.methods.push(createPullProvider(requestor)) + } }) return json @@ -1187,7 +1163,7 @@ const generateEventSubscribers = json => { const tag = notifier.tags.find(tag => tag.name === 'notifier') // if there's an x-event extension, this denotes an editorially created subscriber if (!tag['x-event']) { - tag['x-event'] = methodRename(notifier, name => 'on' + name.charAt(0).toUpperCase() +! name.substring(1)) + tag['x-event'] = methodRename(notifier, name => 'on' + name.charAt(0).toUpperCase() + name.substring(1)) } const subscriber = json.methods.find(method => method.name === tag['x-event']) @@ -1250,7 +1226,7 @@ const generateProviderRegistrars = json => { if (!registration) { json.methods.push({ - name: name + ".provide", + name: `${name}.provide`, tags: [ { "name": "registration", @@ -1605,7 +1581,7 @@ const fireboltize = (json, bidirectional) => { json = generatePropertySetters(json) // TODO: we don't use this yet... consider removing? // json = generatePushPullMethods(json) - // json = generateProvidedByMethods(json) + json = generateProvidedByMethods(json) json = generatePolymorphicPullEvents(json) if (bidirectional) { @@ -1890,7 +1866,7 @@ const getClientModule = (name, client, server) => { let openrpc = JSON.parse(JSON.stringify(client)) openrpc.methods = openrpc.methods - .filter(method => notifierFor(method) && notifierFor(method).startsWith(name + '.') || interfaces.find(name => method.name.startsWith(name + '.'))) + .filter(method => (notifierFor(method) && notifierFor(method).startsWith(name + '.') || interfaces.find(name => method.name.startsWith(name + '.')))) openrpc.info.title = name openrpc.components.schemas = Object.fromEntries(Object.entries(openrpc.components.schemas).filter( ([key, schema]) => key.startsWith('http') || key.split('.')[0] === name)) if (client.info['x-module-descriptions'] && client.info['x-module-descriptions'][name]) { diff --git a/src/validate/index.mjs b/src/validate/index.mjs index 0ef3e5bb..542fe8c0 100644 --- a/src/validate/index.mjs +++ b/src/validate/index.mjs @@ -27,18 +27,27 @@ import addFormats from 'ajv-formats' import url from "url" import path from "path" import fetch from "node-fetch" +import { getCapability } from "../shared/methods.mjs" const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) const run = async ({ input: input, + server: server, + client: client, schemas: schemas, transformations = false, bidirectional = false, 'pass-throughs': passThroughs }) => { - logHeader(`Validating ${path.relative('.', input)} with${transformations ? '' : 'out'} Firebolt transformations.`) + if (input) { + logHeader(`Validating ${path.relative('.', input)} with${transformations ? '' : 'out'} Firebolt transformations.`) + } + else { + logHeader(`Validating ${path.relative('.', server)} with${transformations ? '' : 'out'} Firebolt transformations.`) + + } let invalidResults = 0 @@ -67,8 +76,8 @@ const run = async ({ delete sharedSchemas[path] }) - const moduleList = input ? await readDir(path.join(input), { recursive: true }) : [] - const modules = await readFiles(moduleList, path.join(input)) + const moduleList = input ? await readDir(path.join(input), { recursive: true }) : [server] + const modules = await readFiles(moduleList, input ? path.join(input) : '.') let descriptionsList, markdown @@ -193,132 +202,106 @@ const run = async ({ // json = addExternalSchemas(json, sharedSchemas) } + ajv.addSchema(Object.values(json.components.schemas).filter(s => s.$id)) + modules[key] = json }) // Validate all modules examples Object.keys(modules).forEach(key => { - let json = modules[key] - const exampleSpec = { - "$id": "https://meta.rdkcentral.com/firebolt/dynamic/" + (json.info.title) +"/examples", - "title": "FireboltOpenRPCExamples", - "definitions": { - "Document": { - "type": "object", - "properties": { - "methods": { - "type": "array", - "items": { - "allOf": json.methods.filter(method => method.result).map(method => ({ - "if": { - "type": "object", - "properties": { - "name": { - "const": method.name + const json = modules[key] + json.methods.forEach((method, index) => { + const exampleSpec = { + "$id": `${json.info.title}.method.${index}.examples`, + "title": `${method.name} Examples`, + "oneOf": [], + "components": json.components + } + + exampleSpec.oneOf.push({ + type: "object", + required: ["examples"], + properties: { + examples: { + type: "array", + items: { + type: "object", + properties: { + params: method.params.length ? { + "type": "array", + "items": { + "allOf": method.params.map(param => ({ + "if": { + "type": "object", + "properties": { + "name": { + "const": param.name + } + } + }, + "then": { + "type": "object", + "properties": { + "value": param.schema + } } - } + })) }, - "then": { - "type": "object", - "properties": { - "examples": { - "type": "array", - "items": { - "type": "object", - "properties": { - "result": { - "type": "object", - "properties": { - "value": method.result.schema - } - }, - "params": method.params.length ? { - "type": "array", - "items": { - "allOf": method.params.map(param => ({ - "if": { - "type": "object", - "properties": { - "name": { - "const": param.name - } - } - }, - "then": { - "type": "object", - "properties": { - "value": param.schema - } - } - })) - }, - "if": { - "type": "array" // always true, but avoids an empty allOf below - }, - "then": method.params.filter(p => p.required).length ? { - "allOf": method.params.filter(p => p.required).map(param => ({ - "contains": { - "type": "object", - "properties": { - "name": { - "const": param.name - } - } - } - })) - } : {} - } : {} + "if": { + "type": "array" // always true, but avoids an empty allOf below + }, + "then": method.params.filter(p => p.required).length ? { + "allOf": method.params.filter(p => p.required).map(param => ({ + "contains": { + "type": "object", + "properties": { + "name": { + "const": param.name } } } - } + })) + } : {} + } : {}, + result: method.result?.schema ? { + type: "object", + required: ["value"], + properties: { + value: method.result.schema } - })) + } : {} } } } } - }, - components: { - schemas: {} - } - } - - Object.entries(json.components.schemas).forEach( ([key, schema]) => { - if (key.startsWith("https://")) { - exampleSpec.definitions[key] = schema - } - else { - exampleSpec.components.schemas[key] = schema - } - }) - Object.assign(exampleSpec.definitions, json.components.schemas) + }) - exampleSpec.oneOf = [ - { - "$ref": "#/definitions/Document" + if (method.name === 'Device.distributor') { + console.dir(exampleSpec.oneOf, { depth: 100 }) + console.dir(method.examples) } - ] - const examples = ajv.compile(exampleSpec) + const examples = ajv.compile(exampleSpec) - try { - const exampleResult = validate(json, {}, ajv, examples) - - if (exampleResult.valid) { - printResult(exampleResult, "Firebolt Examples") + try { + const exampleResult = validate(method, { title: json.info.title + '.' + method.name, path: `/methods/${method.name}` }, ajv, examples) + + if (exampleResult.valid) { +// printResult(exampleResult, "Firebolt Examples") + } + else { + printResult(exampleResult, "Firebolt Examples") + + // if (!exampleResult.valid) { + // console.dir(exampleSpec, { depth: 100 }) + // } + } } - else { - printResult(exampleResult, "Firebolt Examples") - -// if (!exampleResult.valid) { -// console.dir(exampleSpec, { depth: 100 }) -// } + catch (error) { + throw error } - } - catch (error) { - throw error - } + }) + }) // Remove the shared schemas, because they're bundled into the OpenRPC docs @@ -340,10 +323,10 @@ const run = async ({ printResult(fireboltResult, "Firebolt") } - if (passThroughs) { + if (false && passThroughs) { const passthroughResult = validatePasshtroughs(json) printResult(passthroughResult, "Firebolt App pass-through") - } + } } catch (error) { throw error @@ -352,7 +335,7 @@ const run = async ({ if (invalidResults) { console.error(`\nExiting due to ${invalidResults} invalid document${invalidResults === 1 ? '' : 's'}.\n`) - process.exit(-1) + process.abort() } return Promise.resolve() } diff --git a/src/validate/validator/index.mjs b/src/validate/validator/index.mjs index 687aaada..2d4ecbae 100644 --- a/src/validate/validator/index.mjs +++ b/src/validate/validator/index.mjs @@ -19,10 +19,11 @@ import groupBy from 'array.prototype.groupby' import util from 'util' import { getPayloadFromEvent } from '../../shared/modules.mjs' import { getPropertiesInSchema, getPropertySchema } from '../../shared/json-schema.mjs' +import { getCapability, getRole } from '../../shared/methods.mjs' -const addPrettyPath = (error, json) => { +const addPrettyPath = (error, json, info) => { const path = [] - const root = json.title || json.info.title + const root = json.title || json.info?.title || info?.title || `Unknown` let pointer = json error.instancePath.substr(1).split('/').forEach(x => { @@ -35,6 +36,8 @@ const addPrettyPath = (error, json) => { pointer = pointer[x] } }) + + error.instancePath = (info.path ? info.path : '') + error.instancePath error.prettyPath = '/' + path.join('/') error.document = root error.node = pointer @@ -127,6 +130,11 @@ export const displayError = (error) => { console.error(`\t\x1b[2m${pad('document:')}\x1b[0m\x1b[38;5;208m${error.document}\x1b[0m \x1b[2m(${errorFileType})\x1b[2m\x1b[0m`) console.error(`\t\x1b[2m${pad('source:')}\x1b[0m\x1b[38;5;208m${error.source}\x1b[0m`) + if (error.capability) { + console.error(`\t\x1b[2m${pad('capability:')}\x1b[0m\x1b[38;5;208m${error.capability}\x1b[0m`) + console.error(`\t\x1b[2m${pad('role:')}\x1b[0m\x1b[38;5;208m${error.role}\x1b[0m`) + } + if (error.value) { console.error(`\t\x1b[2m${pad('value:')}\x1b[0m\n`) console.dir(error.value, {depth: null, colors: true})// + JSON.stringify(example, null, ' ') + '\n') @@ -139,9 +147,8 @@ export const displayError = (error) => { console.error() } -export const validate = (json = {}, schemas = {}, ajv, validator, additionalPackages = []) => { +export const validate = (json = {}, info = {}, ajv, validator, additionalPackages = []) => { let valid = validator(json) - let root = json.title || json.info.title const errors = [] if (valid) { @@ -150,7 +157,7 @@ export const validate = (json = {}, schemas = {}, ajv, validator, additionalPack const additionalValid = addtnlValidator(json) if (!additionalValid) { valid = false - addtnlValidator.errors.forEach(error => addPrettyPath(error, json)) + addtnlValidator.errors.forEach(error => addPrettyPath(error, json, info)) addtnlValidator.errors.forEach(error => error.source = 'Firebolt OpenRPC') addtnlValidator.errors.forEach(error => addFailingMethodSchema(error, json, addtnlValidator.schema)) errors.push(...pruneErrors(addtnlValidator.errors)) @@ -159,13 +166,21 @@ export const validate = (json = {}, schemas = {}, ajv, validator, additionalPack } } else { - validator.errors.forEach(error => addPrettyPath(error, json)) + validator.errors.forEach(error => addPrettyPath(error, json, info)) validator.errors.forEach(error => error.source = 'OpenRPC') + json.methods && validator.errors.forEach(error => { + if (error.instancePath.startsWith('/methods/')) { + const method = json.methods[parseInt(error.instancePath.split('/')[2])] + error.capability = getCapability(method) + error.role = getRole(method) + } + }) + errors.push(...pruneErrors(validator.errors)) } - return { valid: valid, title: json.title || json.info.title, errors: errors } + return { valid: valid, title: json.title || info?.title || json.info?.title, errors: errors } } const schemasMatch = (a, b) => { @@ -219,12 +234,12 @@ export const validatePasshtroughs = (json) => { examples2 = provider.examples.map(e => e.params[e.params.length-1].value) } else { - destination = method.result.schema - examples1 = method.examples.map(e => e.result.value) - source = JSON.parse(JSON.stringify(provider.tags.find(t => t['x-response'])['x-response'])) - sourceName = provider.tags.find(t => t['x-response'])['x-response-name'] - examples2 = provider.tags.find(t => t['x-response'])['x-response'].examples - delete source.examples + // destination = method.result.schema + // examples1 = method.examples.map(e => e.result.value) + // source = JSON.parse(JSON.stringify(provider.tags.find(t => t['x-response'])['x-response'])) + // sourceName = provider.tags.find(t => t['x-response'])['x-response-name'] + // examples2 = provider.tags.find(t => t['x-response'])['x-response'].examples + // delete source.examples } if (!schemasMatch(source, destination)) {