Skip to content

Commit

Permalink
feat: JavaScript now has distinct imports for shared schemas
Browse files Browse the repository at this point in the history
  • Loading branch information
jlacivita committed Mar 27, 2024
1 parent 74136cd commit 7d70f92
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 110 deletions.
6 changes: 5 additions & 1 deletion languages/javascript/language.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@
"/index.mjs",
"/defaults.mjs"
],
"templatesPerSchema": [
"/index.mjs"
],
"createModuleDirectories": true,
"copySchemasIntoModules": true,
"copySchemasIntoModules": false,
"mergeOnTitle": true,
"aggregateFiles": [
"/index.d.ts"
],
Expand Down
27 changes: 27 additions & 0 deletions languages/javascript/templates/schemas/index.mjs
Original file line number Diff line number Diff line change
@@ -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} */

}
1 change: 1 addition & 0 deletions languages/javascript/templates/types/namespace.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
${if.namespace.notsame}${parent.Title}.${end.if.namespace.notsame}
56 changes: 33 additions & 23 deletions src/macrofier/engine.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}\}`
Expand Down Expand Up @@ -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))
}

Expand All @@ -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] = {
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -973,7 +983,9 @@ function generateSchemas(json, templates, options) {
enum: isEnum(schema)
}

results.push(result)
if (result.name) {
results.push(result)
}
}

let list = []
Expand All @@ -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])
})
}
}
})

Expand All @@ -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))
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 }) : ''
Expand Down Expand Up @@ -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
Expand Down
86 changes: 41 additions & 45 deletions src/macrofier/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 **********************************************/
Expand All @@ -47,6 +47,7 @@ const macrofy = async (
createPolymorphicMethods,
createModuleDirectories,
copySchemasIntoModules,
mergeOnTitle,
extractSubSchemas,
unwrapResultObjects,
allocatedPrimitiveProxies,
Expand Down Expand Up @@ -91,6 +92,7 @@ const macrofy = async (
engine.setTyper(typer)
engine.setConfig({
copySchemasIntoModules,
mergeOnTitle,
createModuleDirectories,
extractSubSchemas,
unwrapResultObjects,
Expand Down Expand Up @@ -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]))
Expand Down Expand Up @@ -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
}
})

Expand Down
Loading

0 comments on commit 7d70f92

Please sign in to comment.