From 7d70f92b569240dc77a9aca02f2adb692796c3b2 Mon Sep 17 00:00:00 2001 From: Jeremy LaCivita Date: Wed, 27 Mar 2024 16:12:37 -0400 Subject: [PATCH] feat: JavaScript now has distinct imports for shared schemas --- languages/javascript/language.config.json | 6 +- .../javascript/templates/schemas/index.mjs | 27 ++++++ .../javascript/templates/types/namespace.mjs | 1 + src/macrofier/engine.mjs | 56 +++++++----- src/macrofier/index.mjs | 86 +++++++++---------- src/macrofier/types.mjs | 62 +++++++------ src/sdk/index.mjs | 1 + src/shared/json-schema.mjs | 3 +- src/shared/modules.mjs | 12 --- 9 files changed, 144 insertions(+), 110 deletions(-) create mode 100644 languages/javascript/templates/schemas/index.mjs create mode 100644 languages/javascript/templates/types/namespace.mjs diff --git a/languages/javascript/language.config.json b/languages/javascript/language.config.json index 19b4659d..12bd94e8 100644 --- a/languages/javascript/language.config.json +++ b/languages/javascript/language.config.json @@ -5,8 +5,12 @@ "/index.mjs", "/defaults.mjs" ], + "templatesPerSchema": [ + "/index.mjs" + ], "createModuleDirectories": true, - "copySchemasIntoModules": true, + "copySchemasIntoModules": false, + "mergeOnTitle": true, "aggregateFiles": [ "/index.d.ts" ], diff --git a/languages/javascript/templates/schemas/index.mjs b/languages/javascript/templates/schemas/index.mjs new file mode 100644 index 00000000..44c84896 --- /dev/null +++ b/languages/javascript/templates/schemas/index.mjs @@ -0,0 +1,27 @@ +/* + * Copyright 2021 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* ${IMPORTS} */ + +/* ${INITIALIZATION} */ + +export default { + + /* ${ENUMS} */ + + } \ No newline at end of file diff --git a/languages/javascript/templates/types/namespace.mjs b/languages/javascript/templates/types/namespace.mjs new file mode 100644 index 00000000..f5894dc9 --- /dev/null +++ b/languages/javascript/templates/types/namespace.mjs @@ -0,0 +1 @@ +${if.namespace.notsame}${parent.Title}.${end.if.namespace.notsame} \ No newline at end of file diff --git a/src/macrofier/engine.mjs b/src/macrofier/engine.mjs index 69ac1505..14c80927 100644 --- a/src/macrofier/engine.mjs +++ b/src/macrofier/engine.mjs @@ -31,7 +31,7 @@ const { isObject, isArray, propEq, pathSatisfies, propSatisfies } = predicates import { isRPCOnlyMethod, isProviderInterfaceMethod, getProviderInterface, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs, isAllowFocusMethod, hasAllowFocusMethods, createPolymorphicMethods, isExcludedMethod, isCallsMetricsMethod } from '../shared/modules.mjs' import isEmpty from 'crocks/core/isEmpty.js' -import { getPath as getJsonPath, getLinkedSchemaPaths, getSchemaConstraints, isSchema, localizeDependencies, isDefinitionReferencedBySchema, mergeAnyOf, mergeOneOf, getSafeEnumKeyName, getAllValuesForName, getReferencedSchema } from '../shared/json-schema.mjs' +import { getReferencedSchema, getLinkedSchemaPaths, getSchemaConstraints, isSchema, localizeDependencies, isDefinitionReferencedBySchema, mergeAnyOf, mergeOneOf, getSafeEnumKeyName, getAllValuesForName } from '../shared/json-schema.mjs' // util for visually debugging crocks ADTs @@ -134,15 +134,16 @@ const getTemplateForExampleResult = (method, templates) => { const getLinkForSchema = (schema, json) => { const dirs = config.createModuleDirectories const copySchemasIntoModules = config.copySchemasIntoModules + const definitions = json.definitions || json.components.schemas const type = types.getSchemaType(schema, json, { templateDir: state.typeTemplateDir, destination: state.destination, section: state.section }) // local - insert a bogus link, that we'll update later based on final table-of-contents - if (json.components.schemas[type]) { + if (definitions && definitions[type]) { return `#\$\{LINK:schema:${type}\}` } else { - const [group, schema] = Object.entries(json.components.schemas).find(([key, value]) => json.components.schemas[key] && json.components.schemas[key][type]) || [null, null] + const [group, schema] = Object.entries(definitions).find(([key, value]) => definitions[key] && definitions[key][type]) || [null, null] if (group && schema) { if (copySchemasIntoModules) { return `#\$\{LINK:schema:${type}\}` @@ -371,12 +372,12 @@ const generateAggregateMacros = (openrpc, modules, templates, library) => Object } template = getTemplate('/codeblocks/mock-import', templates) - if (template) { + if (template && module.info) { acc.mockImports += insertMacros(template + '\n', generateMacros(module, templates)) } template = getTemplate('/codeblocks/mock-parameter', templates) - if (template) { + if (template && module.info) { acc.mockObjects += insertMacros(template + '\n', generateMacros(module, templates)) } @@ -400,7 +401,7 @@ const getPromotionNameFromContentDescriptor = (descriptor, prefix) => { } const promoteSchema = (location, property, title, document, destinationPath) => { - const destination = getJsonPath(destinationPath, document) + const destination = getReferencedSchema(destinationPath, document) destination[title] = location[property] destination[title].title = title location[property] = { @@ -415,6 +416,8 @@ const isSubSchema = (schema) => schema.type === 'object' || (schema.type === 'st const isSubEnumOfArraySchema = (schema) => (schema.type === 'array' && schema.items.enum) const promoteAndNameSubSchemas = (obj) => { + const moduleTitle = module.info ? module.info.title : module.title + // make a copy so we don't polute our inputs obj = JSON.parse(JSON.stringify(obj)) // find anonymous method param or result schemas and name/promote them @@ -435,7 +438,7 @@ const promoteAndNameSubSchemas = (obj) => { method.tags.forEach(tag => { if (tag['x-error']) { const descriptor = { - name: obj.info.title + 'Error', + name: moduleTitle + 'Error', schema: tag['x-error'] } addContentDescriptorSubSchema(descriptor, '', obj) @@ -606,6 +609,8 @@ const generateMacros = (obj, templates, languages, options = {}) => { const moduleInclude = getTemplate(suffix ? `/codeblocks/module-include.${suffix}` : '/codeblocks/module-include', templates) const moduleIncludePrivate = getTemplate(suffix ? `/codeblocks/module-include-private.${suffix}` : '/codeblocks/module-include-private', templates) const moduleInit = getTemplate(suffix ? `/codeblocks/module-init.${suffix}` : '/codeblocks/module-init', templates) + const moduleTitle = obj.info ? obj.info.title : obj.title + const moduleDescription = obj.info ? obj.info.description : obj.description Object.assign(macros, { imports, @@ -617,8 +622,8 @@ const generateMacros = (obj, templates, languages, options = {}) => { providerInterfaces, providerSubscribe, version: getSemanticVersion(obj), - title: obj.info.title, - description: obj.info.description, + title: moduleTitle, + description: moduleDescription, module: module, moduleInclude: moduleInclude, moduleIncludePrivate: moduleIncludePrivate, @@ -925,6 +930,11 @@ function generateSchemas(json, templates, options) { if (['ListenResponse', 'ProviderRequest', 'ProviderResponse', 'FederatedResponse', 'FederatedRequest'].includes(name)) { return } + + if (!schema.title) { + return + } + let content = getTemplate('/schemas/default', templates) if (!schema.examples || schema.examples.length === 0) { @@ -973,7 +983,9 @@ function generateSchemas(json, templates, options) { enum: isEnum(schema) } - results.push(result) + if (result.name) { + results.push(result) + } } let list = [] @@ -983,10 +995,12 @@ function generateSchemas(json, templates, options) { if (isSchema(schema) && !schema.$id) { list.push([name, schema]) } - else if (isSchema(schema) && schema.$id && schema.definitions) { - Object.entries(schema.definitions).forEach( ([name, schema]) => { - list.push([name, schema]) - }) + else if (json.info && isSchema(schema) && schema.$id && schema.definitions) { + if ( (config.mergeOnTitle && (schema.title === json.info.title)) || config.copySchemasIntoModules) { + Object.entries(schema.definitions).forEach( ([name, schema]) => { + list.push([name, schema]) + }) + } } }) @@ -1004,11 +1018,6 @@ function getRelatedSchemaLinks(schema = {}, json = {}, templates = {}, options = // - convert them to the $ref value (which are paths to other schema files), instead of the path to the ref node itself // - convert those into markdown links of the form [Schema](Schema#/link/to/element) - console.dir(getLinkedSchemaPaths(schema) - .map(path => getPathOr(null, path, schema)) -// .map(ref => getReferencedSchema(ref, json)) -) - let links = getLinkedSchemaPaths(schema) .map(path => getPathOr(null, path, schema)) .filter(path => seen.hasOwnProperty(path) ? false : (seen[path] = true)) @@ -1072,9 +1081,10 @@ const generateImports = (json, templates, options = { destination: '' }) => { let template = getTemplateFromDestination(options.destination, '/imports/default', templates) const subschemas = getAllValuesForName("$id", json) + const subschemaLocation = json.definitions || json.components && json.components.schemas || {} subschemas.shift() // remove main $id - if (subschemas.length && !json.info['x-uri-titles']) { - imports += subschemas.map(id => json.components.schemas[id].title).map(shared => template.replace(/\$\{info.title.lowercase\}/g, shared.toLowerCase())).join('') + if (subschemas.length) { + imports += subschemas.map(id => subschemaLocation[id].title).map(shared => template.replace(/\$\{info.title.lowercase\}/g, shared.toLowerCase())).join('') } // TODO: this does the same as above? am i missing something? // let componentExternalSchema = getComponentExternalSchema(json) @@ -1332,7 +1342,7 @@ function insertMethodMacros(template, methodObj, json, templates, type = '', exa const pullsForType = pullsResult && types.getSchemaType(pullsResult, json, { destination: state.destination, templateDir: state.typeTemplateDir, section: state.section }) const pullsParamsType = (pullsParams && (type === 'methods')) ? types.getSchemaShape(pullsParams, json, { destination: state.destination, templateDir: state.typeTemplateDir, section: state.section }) : '' const pullsForParamTitle = pullsParams ? pullsParams.title.charAt(0).toLowerCase() + pullsParams.title.substring(1) : '' - const pullsForResultTitle = pullsResult ? pullsResult.title.charAt(0).toLowerCase() + pullsResult.title.substring(1) : '' + const pullsForResultTitle = (pullsResult && pullsResult.title) ? pullsResult.title.charAt(0).toLowerCase() + pullsResult.title.substring(1) : '' const pullsResponseInit = (pullsParams && (type === 'methods')) ? types.getSchemaShape(pullsParams, json, { templateDir: 'result-initialization', property: pullsForParamTitle, required: pullsParams.required, destination: state.destination, section: state.section, primitive: true, skipTitleOnce: true }) : '' const pullsResponseInst = (pullsParams && (type === 'methods')) ? types.getSchemaShape(pullsParams, json, { templateDir: 'result-instantiation', property: pullsForParamTitle, required: pullsParams.required, destination: state.destination, section: state.section, primitive: true, skipTitleOnce: true }) : '' const pullsResultSerialize = (pullsResult && (type === 'methods')) ? types.getSchemaShape(pullsResult, json, { templateDir: 'parameter-serialization/sub-property', property: pullsForResultTitle, required: pullsResult.required, destination: state.destination, section: state.section, primitive: true, skipTitleOnce: true }) : '' @@ -1644,7 +1654,7 @@ function generateResultParams(result, json, templates, { name = '' } = {}) { if (result.$ref.includes("/x-schemas/")) { moduleTitle = result.$ref.split("/")[2] } - result = getJsonPath(result.$ref, json) + result = getReferencedSchema(result.$ref, json) } // const results are almost certainly `"const": "null"` so there's no need to include it in the method signature diff --git a/src/macrofier/index.mjs b/src/macrofier/index.mjs index 99a1a3e0..ee40f665 100644 --- a/src/macrofier/index.mjs +++ b/src/macrofier/index.mjs @@ -25,7 +25,7 @@ import { getModule, hasPublicAPIs } from '../shared/modules.mjs' import { logHeader, logSuccess } from '../shared/io.mjs' import path from 'path' import engine from './engine.mjs' -import { getLocalSchemas, replaceRef } from '../shared/json-schema.mjs' +import { getLocalSchemas, replaceRef, replaceUri } from '../shared/json-schema.mjs' /************************************************************************************************/ /******************************************** MAIN **********************************************/ @@ -47,6 +47,7 @@ const macrofy = async ( createPolymorphicMethods, createModuleDirectories, copySchemasIntoModules, + mergeOnTitle, extractSubSchemas, unwrapResultObjects, allocatedPrimitiveProxies, @@ -91,6 +92,7 @@ const macrofy = async ( engine.setTyper(typer) engine.setConfig({ copySchemasIntoModules, + mergeOnTitle, createModuleDirectories, extractSubSchemas, unwrapResultObjects, @@ -144,7 +146,22 @@ const macrofy = async ( modules = moduleList.map(name => getModule(name, openrpc, copySchemasIntoModules, extractSubSchemas)) } - const aggregateMacros = engine.generateAggregateMacros(openrpc, modules.concat(staticModules), templates, libraryName) + // Grab all schema groups w/ a URI string. These came from some external json-schema that was bundled into the OpenRPC + const externalSchemas = {} + openrpc.components && openrpc.components.schemas + && Object.entries(openrpc.components.schemas).filter(([_, schema]) => schema.$id).forEach(([name, schema]) => { + const id = schema.$id + externalSchemas[id] = JSON.parse(JSON.stringify(schema)) + replaceUri(id, '', externalSchemas[id]) + console.dir(Object.keys(externalSchemas[id].definitions)) + Object.values(openrpc.components.schemas).forEach(schema => { + if (schema.$id && schema.$id !== id) { + externalSchemas[id].definitions[schema.$id] = schema + } + }) + }) + + const aggregateMacros = engine.generateAggregateMacros(openrpc, modules.concat(staticModules).concat(Object.values(externalSchemas)), templates, libraryName) const outputFiles = Object.fromEntries(Object.entries(await readFiles( staticCodeList, staticContent)) .map( ([n, v]) => [path.join(output, n), v])) @@ -240,60 +257,39 @@ const macrofy = async ( } }) } - - // Grab all schema groups w/ a URI string. These came from some external json-schema that was bundled into the OpenRPC - const externalSchemas = {} - openrpc['x-schemas'] - && Object.entries(openrpc['x-schemas']).forEach(([name, schema]) => { - if (schema.uri) { - const id = schema.uri - externalSchemas[id] = externalSchemas[id] || { $id: id, info: {title: name }, methods: []} - externalSchemas[id].components = externalSchemas[id].components || {} - externalSchemas[id].components.schemas = externalSchemas[id].components.schemas || {} - externalSchemas[id]['x-schemas'] = JSON.parse(JSON.stringify(openrpc['x-schemas'])) - - const schemas = JSON.parse(JSON.stringify(schema)) - delete schemas.uri - Object.assign(externalSchemas[id].components.schemas, schemas) - } - }) - - // update the refs - Object.values(externalSchemas).forEach( document => { - getLocalSchemas(document).forEach((path) => { - const parts = path.split('/') - // Drop the grouping path element, since we've pulled this schema out into it's own document - if (parts.length === 4 && path.startsWith('#/x-schemas/' + document.info.title + '/')) { - replaceRef(path, ['#/components/schemas', parts[3]].join('/'), document) - } - // Add the fully qualified URI for any schema groups other than this one - else if (parts.length === 4 && path.startsWith('#/x-schemas/')) { - const uri = openrpc['x-schemas'][parts[2]].uri - // store the case-senstive group title for later use - document.info['x-uri-titles'] = document.info['x-uri-titles'] || {} - document.info['x-uri-titles'][uri] = document.info.title - openrpc.info['x-uri-titles'] = openrpc.info['x-uri-titles'] || {} - openrpc.info['x-uri-titles'][uri] = document.info.title - replaceRef(path, '#/x-schemas/' + parts[2] + '/' + parts[3], document) - } - }) - }) // Output any schema templates for each bundled external schema document Object.values(externalSchemas).forEach( document => { - if (templatesPerSchema) { - templatesPerSchema.forEach( t => { + if (mergeOnTitle && modules.find(m => m.info.title === document.title)) { + console.log('Skipping: ' + document.title + ': ' + document.$id) + return // skip this one, it was already merged into the module w/ the same name + } + + console.log(document.title + ': ' + document.$id) + if (templatesPerSchema || primaryOutput.length) { + templatesPerSchema && templatesPerSchema.forEach( t => { const macros = engine.generateMacros(document, templates, exampleTemplates, {hideExcluded: hideExcluded, createPolymorphicMethods: createPolymorphicMethods, destination: t}) let content = getTemplate('/schemas', t, templates) - - // NOTE: whichever insert is called first also needs to be called again last, so each phase can insert recursive macros from the other content = engine.insertMacros(content, macros) - const location = createModuleDirectories ? path.join(output, document.info.title, t) : path.join(output, t.replace(/module/, document.info.title.toLowerCase()).replace(/index/, document.info.title)) + const location = createModuleDirectories ? path.join(output, document.title, t) : path.join(output, t.replace(/module/, document.title.toLowerCase()).replace(/index/, document.title)) outputFiles[location] = content logSuccess(`Generated macros for schema ${path.relative(output, location)}`) }) + + primaryOutput && primaryOutput.forEach(output => { + console.log(`appending ${document.title} schemas to ${output}`) + console.dir(Object.keys(document.definitions), { depth: 100 }) + if (document.title === 'Entertainment') { + console.dir(Object.keys(document.definitions['https://meta.rdkcentral.com/firebolt/schemas/types'].definitions)) + } + const macros = engine.generateMacros(document, templates, exampleTemplates, {hideExcluded: hideExcluded, copySchemasIntoModules: copySchemasIntoModules, createPolymorphicMethods: createPolymorphicMethods, destination: output}) + macros.append = append + outputFiles[output] = engine.insertMacros(outputFiles[output], macros) + }) + + append = true } }) diff --git a/src/macrofier/types.mjs b/src/macrofier/types.mjs index 55ef7b7e..08a962c6 100644 --- a/src/macrofier/types.mjs +++ b/src/macrofier/types.mjs @@ -17,7 +17,7 @@ */ import deepmerge from 'deepmerge' -import { getPath, localizeDependencies, getSafeEnumKeyName } from '../shared/json-schema.mjs' +import { getReferencedSchema, localizeDependencies, getSafeEnumKeyName } from '../shared/json-schema.mjs' import path from "path" let convertTuplesToArraysOrObjects = false @@ -149,11 +149,13 @@ const getXSchemaGroupFromProperties = (schema, title, properties, group) => { // TODO: this assumes the same title doesn't exist in multiple x-schema groups! const getXSchemaGroup = (schema, module) => { - let group = module.info.title + let group = module.info ? module.info.title : module.title + let bundles = module.definitions || module.components.schemas - if (schema.title && module['x-schemas']) { - Object.entries(module['x-schemas']).forEach(([title, module]) => { - Object.values(module).forEach(moduleSchema => { + if (schema.title && bundles) { + Object.entries(bundles).filter(([key, s]) => s.$id).forEach(([id, bundle]) => { + const title = bundle.title + Object.values(bundle.definitions).forEach(moduleSchema => { let schemas = moduleSchema.allOf ? moduleSchema.allOf : [moduleSchema] schemas.forEach((s) => { if (schema.title === s.title || schema.title === moduleSchema.title) { @@ -173,8 +175,8 @@ function getSchemaDescription(schema, module) { if (schema.type === 'array' && schema.items) { schema = schema.items } - if (schema['$ref'] && (schema['$ref'][0] === '#')) { - const refSchema = getPath(schema['$ref'], module) + if (schema['$ref']) { + const refSchema = getReferencedSchema(schema['$ref'], module) description = (refSchema && refSchema.description) || description } return description @@ -182,7 +184,8 @@ function getSchemaDescription(schema, module) { function insertSchemaMacros(content, schema, module, { name = '', parent = '', property = '', required = false, recursive = true, templateDir = 'types'}) { const title = name || schema.title || '' - const moduleTitle = getXSchemaGroup(schema, module) + const parentTitle = getXSchemaGroup(schema, module) + const moduleTitle = module.info ? module.info.title : module.title const description = getSchemaDescription(schema, module) content = content @@ -191,9 +194,9 @@ function insertSchemaMacros(content, schema, module, { name = '', parent = '', p .replace(/\$\{TITLE\}/g, title.toUpperCase()) .replace(/\$\{property\}/g, property) .replace(/\$\{Property\}/g, capitalize(property)) - .replace(/\$\{if\.namespace\.notsame}(.*?)\$\{end\.if\.namespace\.notsame\}/g, (module.info.title !== (parent || moduleTitle)) ? '$1' : '') - .replace(/\$\{parent\.title\}/g, parent || moduleTitle) - .replace(/\$\{parent\.Title\}/g, capitalize(parent || moduleTitle)) + .replace(/\$\{if\.namespace\.notsame}(.*?)\$\{end\.if\.namespace\.notsame\}/g, (moduleTitle !== (parent || parentTitle)) ? '$1' : '') + .replace(/\$\{parent\.title\}/g, parent || parentTitle) + .replace(/\$\{parent\.Title\}/g, capitalize(parent || parentTitle)) .replace(/\$\{description\}/g, description) .replace(/\$\{if\.optional\}(.*?)\$\{end\.if\.optional\}/gms, (Array.isArray(required) ? required.includes(property) : required) ? '' : '$1') .replace(/\$\{if\.non.optional\}(.*?)\$\{end\.if\.non.optional\}/gms, (Array.isArray(required) ? required.includes(property) : required) ? '$1' : '') @@ -201,9 +204,9 @@ function insertSchemaMacros(content, schema, module, { name = '', parent = '', p .replace(/\$\{summary\}/g, description ? description.split('\n')[0] : '') .replace(/\$\{name\}/g, title) .replace(/\$\{NAME\}/g, title.toUpperCase()) - .replace(/\$\{info.title\}/g, moduleTitle) - .replace(/\$\{info.Title\}/g, capitalize(moduleTitle)) - .replace(/\$\{info.TITLE\}/g, moduleTitle.toUpperCase()) + .replace(/\$\{info.title\}/g, parentTitle) + .replace(/\$\{info.Title\}/g, capitalize(parentTitle)) + .replace(/\$\{info.TITLE\}/g, parentTitle.toUpperCase()) if (recursive) { content = content.replace(/\$\{type\}/g, getSchemaType(schema, module, { templateDir: templateDir, destination: state.destination, section: state.section, code: false, namespace: true })) @@ -253,6 +256,8 @@ const insertObjectAdditionalPropertiesMacros = (content, schema, module, title, jsonType = 'string' } + const moduleTitle = module.info ? module.info.title : module.title + const additionalType = getPrimitiveType(jsonType, 'additional-types') let namespace = '' @@ -262,8 +267,8 @@ const insertObjectAdditionalPropertiesMacros = (content, schema, module, title, let parent = getXSchemaGroup(propertyNames, module) key = propertyNames.title namespace = getTemplate(path.join(options.templateDir, 'namespace')) - .replace(/\$\{if\.namespace\.notsame}(.*?)\$\{end\.if\.namespace\.notsame\}/g, (module.info.title !== (parent || moduleTitle)) ? '$1' : '') - .replace(/\$\{parent\.Title\}/g, (parent && module.info.title !== parent) ? parent : '') + .replace(/\$\{if\.namespace\.notsame}(.*?)\$\{end\.if\.namespace\.notsame\}/g, (moduleTitle !== (parent || moduleTitle)) ? '$1' : '') + .replace(/\$\{parent\.Title\}/g, (parent && moduleTitle !== parent) ? parent : '') defaultKey = false } content = content @@ -542,6 +547,7 @@ function getSchemaShape(schema = {}, module = {}, { templateDir = 'types', paren const suffix = destination && ('.' + destination.split('.').pop()) || '' const theTitle = insertSchemaMacros(getTemplate(path.join(templateDir, 'title' + suffix)), schema, module, { name: schema.title, parent, property, required, recursive: false }) + const moduleTitle = module.info ? module.info.title : module.title let result = getTemplate(path.join(templateDir, 'default' + suffix)) || '${shape}' @@ -552,11 +558,11 @@ function getSchemaShape(schema = {}, module = {}, { templateDir = 'types', paren } if (schema['$ref']) { - const someJson = getPath(schema['$ref'], module) + const someJson = getReferencedSchema(schema['$ref'], module) if (someJson) { return getSchemaShape(someJson, module, { templateDir, parent, property, required, parentLevel, level, summary, descriptions, destination, enums, array, primitive }) } - throw "Unresolvable $ref: " + schema['ref'] + ", in " + module.info.title + throw "Unresolvable $ref: " + schema['$ref'] + ", in " + moduleTitle } else if (schema.hasOwnProperty('const')) { const shape = insertConstMacros(getTemplate(path.join(templateDir, 'const' + suffix)) || genericTemplate, schema, module, theTitle) @@ -629,7 +635,7 @@ function getSchemaShape(schema = {}, module = {}, { templateDir = 'types', paren } } - let union = deepmerge.all([...schema.allOf.map(x => x['$ref'] ? getPath(x['$ref'], module) || x : x).reverse()], { + let union = deepmerge.all([...schema.allOf.map(x => x['$ref'] ? getReferencedSchema(x['$ref'], module) || x : x).reverse()], { customMerge: merger }) @@ -719,6 +725,7 @@ function getSchemaType(schema, module, { destination, templateDir = 'types', lin schema = sanitize(schema) + const moduleTitle = module.info ? module.info.title : module.title const suffix = destination && ('.' + destination.split('.').pop()) || '' const namespaceStr = namespace ? getTemplate(path.join(templateDir, 'namespace' + suffix)) : '' const theTitle = insertSchemaMacros(namespaceStr + getTemplate(path.join(templateDir, 'title' + suffix)), schema, module, { name: schema.title, parent: getXSchemaGroup(schema, module), recursive: false }) @@ -727,14 +734,13 @@ function getSchemaType(schema, module, { destination, templateDir = 'types', lin const title = schema.type === "object" || Array.isArray(schema.type) && schema.type.includes("object") || schema.enum ? true : false if (schema['$ref']) { - if (schema['$ref'][0] === '#') { - const refSchema = getPath(schema['$ref'], module) - const includeNamespace = (module.info.title !== getXSchemaGroup(refSchema, module)) - return getSchemaType(refSchema, module, {destination, templateDir, link, code, asPath, event, result, expandEnums, baseUrl, namespace:includeNamespace })// { link: link, code: code, destination }) + const refSchema = getReferencedSchema(schema['$ref'], module) + if (refSchema) { + const includeNamespace = (moduleTitle !== getXSchemaGroup(refSchema, module)) + return getSchemaType(refSchema, module, {destination, templateDir, link, code, asPath, event, result, expandEnums, baseUrl, namespace:includeNamespace })// { link: link, code: code, destination }) } else { // TODO: This never happens... but might be worth keeping in case we link to an opaque external schema at some point? - if (link) { return '[' + wrap(theTitle, code ? '`' : '') + '](' + schema['$ref'] + ')' } @@ -786,7 +792,7 @@ function getSchemaType(schema, module, { destination, templateDir = 'types', lin else if ((schema.type === 'object' || (schema.type === 'array')) && schema.title) { const maybeGetPath = (path, json) => { try { - return getPath(path, json) + return getReferencedSchema(path, json) } catch (e) { return null @@ -806,7 +812,7 @@ function getSchemaType(schema, module, { destination, templateDir = 'types', lin let firstItem if (Array.isArray(schema.items)) { if (!isHomogenous(schema.items)) { - console.log(`Non-homogenous tuples not supported: ${schema.items} in ${module.info.title}, ${theTitle}`) + console.log(`Non-homogenous tuples not supported: ${schema.items} in ${moduleTitle}, ${theTitle}`) return '' } firstItem = schema.items[0] @@ -838,7 +844,7 @@ function getSchemaType(schema, module, { destination, templateDir = 'types', lin return template } else if (schema.allOf) { - let union = deepmerge.all([...schema.allOf.map(x => x['$ref'] ? getPath(x['$ref'], module) || x : x)]) + let union = deepmerge.all([...schema.allOf.map(x => x['$ref'] ? getReferencedSchema(x['$ref'], module) || x : x)]) if (schema.title) { union.title = schema.title } @@ -906,7 +912,7 @@ function getJsonType(schema, module, { destination, link = false, title = false, if (schema['$ref'][0] === '#') { //Ref points to local schema //Get Path to ref in this module and getSchemaType - let definition = getPath(schema['$ref'], module) + let definition = getReferencedSchema(schema['$ref'], module) type = getJsonType(definition, schema, {destination}) } } diff --git a/src/sdk/index.mjs b/src/sdk/index.mjs index f6fd0f5e..f78ac84d 100755 --- a/src/sdk/index.mjs +++ b/src/sdk/index.mjs @@ -63,6 +63,7 @@ const run = async ({ primitives: config.primitives, createModuleDirectories: config.createModuleDirectories, copySchemasIntoModules: config.copySchemasIntoModules, + mergeOnTitle: config.mergeOnTitle, extractSubSchemas: config.extractSubSchemas, convertTuplesToArraysOrObjects: config.convertTuplesToArraysOrObjects, unwrapResultObjects: config.unwrapResultObjects, diff --git a/src/shared/json-schema.mjs b/src/shared/json-schema.mjs index a2eba9df..b20a7825 100644 --- a/src/shared/json-schema.mjs +++ b/src/shared/json-schema.mjs @@ -172,8 +172,9 @@ const getReferencedSchema = (uri = '', moduleJson = {}) => { } if (mainPath) { -// throw `Cannot call getReferencedSchema with a fully qualified URI: ${uri}` + // TODO... assuming that bundles are in one of these two places is dangerous, should write a quick method to "find" where they are result = getPathOr(null, ['components', 'schemas', mainPath, ...subPath.slice(1).split('/')], moduleJson) + || getPathOr(null, ['definitions', mainPath, ...subPath.slice(1).split('/')], moduleJson) } else if (subPath) { result = getPathOr(null, subPath.slice(1).split('/'), moduleJson) diff --git a/src/shared/modules.mjs b/src/shared/modules.mjs index 63e5639b..743fe5e2 100644 --- a/src/shared/modules.mjs +++ b/src/shared/modules.mjs @@ -1251,23 +1251,17 @@ const addExternalSchemas = (json, sharedSchemas) => { json.components = json.components || {} json.components.schemas = json.components.schemas || {} - console.dir(Object.keys(sharedSchemas)) - let found = true const added = [] while (found) { const ids = getAllValuesForName('$ref', json) - console.dir(ids.filter(id => id.indexOf("/schemas/types") >= 0)) found = false Object.entries(sharedSchemas).forEach( ([key, schema], i) => { - console.log(`- checking ${key}`) - console.dir(added) if (!added.includes(key)) { if (ids.find(id => id.startsWith(key))) { const bundle = JSON.parse(JSON.stringify(schema)) replaceUri('', bundle.$id, bundle) json.components.schemas[key] = bundle - console.log(`- adding ${key}`) added.push(key) found = true } @@ -1291,7 +1285,6 @@ const removeUnusedSchemas = (json) => { const used = refs.includes(path + '/' + name) || ((name.startsWith('https://') && refs.find(ref => ref.startsWith(name)))) //isDefinitionReferencedBySchema(path + '/' + name, json) if (!used) { delete schema[name] - console.log(`Deleting ${name}`) deleted = true } else { @@ -1325,23 +1318,18 @@ const removeUnusedBundles = (json) => { Object.keys(json.components.schemas).forEach (key => { if (key.startsWith('https://')) { sharedSchemas[key] = json.components.schemas[key] - console.log(`Removing ${key}`) delete json.components.schemas[key] } }) - console.dir(Object.keys(sharedSchemas)) - // and only add back in the ones that are still referenced let found = true while(found) { found = false const ids = [ ...new Set(getAllValuesForName('$ref', json).map(ref => ref.split('#').shift()))] - console.dir(ids) Object.keys(sharedSchemas).forEach(key => { if (ids.includes(key)) { json.components.schemas[key] = sharedSchemas[key] - console.log(`Including ${key}`) delete sharedSchemas[key] found = true }