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

Feat: Bidirectional openrpc gen #235

Merged
merged 4 commits into from
Dec 13, 2024
Merged
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config=jest.config.json --detectOpenHandles",
"build": "npm run validate && npm run build:docs && npm run build:sdk",
"validate": "node ./src/cli.mjs validate --input ./test/openrpc --schemas test/schemas --transformations && npm run build:openrpc && node ./src/cli.mjs validate --input ./build/sdk-open-rpc.json",
"build:openrpc": "node ./src/cli.mjs openrpc --input ./test --template ./src/openrpc-template.json --output ./build/sdk-open-rpc.json --schemas test/schemas",
"build:openrpc": "node ./src/cli.mjs openrpc --input ./test --template ./src/openrpc-template.json --platform-api ./build/sdk-open-rpc.json --app-api ./build./sdk-app-open-rpc.json --schemas test/schemas",
"build:sdk": "node ./src/cli.mjs sdk --input ./build/sdk-open-rpc.json --template ./test/sdk --output ./build/sdk/javascript/src --schemas test/schemas",
"build:d": "node ./src/cli.mjs declarations --input ./build/sdk-open-rpc.json --output ./dist/lib/sdk.d.ts --schemas src/schemas",
"build:docs": "node ./src/cli.mjs docs --input ./build/sdk-open-rpc.json --output ./build/docs/markdown --schemas test/schemas --as-path",
Expand Down
43 changes: 27 additions & 16 deletions src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ import url from 'url'
const knownOpts = {
'input': [path],
'output': [path],
'appApi': [path],
'platformApi': [path],
'sdk': [path],
'schemas': [path, Array],
'template': [path],
'static-module': [String, Array],
'language': [path],
'examples': [path, Array],
'as-path': [Boolean],
'bidirectional': [Boolean],
'pass-throughs': [Boolean]
}

Expand Down Expand Up @@ -49,20 +52,28 @@ const parsedArgs = Object.assign({}, defaults, nopt(knownOpts, shortHands, proce
const task = process.argv[2]
const signOff = () => console.log('\nThis has been a presentation of \x1b[38;5;202mFirebolt\x1b[0m \u{1F525} \u{1F529}\n')

if (task === 'slice') {
slice(parsedArgs).then(signOff)
try {
switch(task) {
case 'slice':
await slice(parsedArgs);
break;
case 'sdk':
await sdk(parsedArgs);
break;
case 'docs':
await docs(parsedArgs);
break;
case 'validate':
await validate(parsedArgs);
break;
case 'openrpc':
await openrpc(parsedArgs);
break;
default:
console.log('Invalid task: ' + task);
}
signOff();
} catch (error) {
console.dir(error)
throw error
}
else if (task === 'sdk') {
sdk(parsedArgs).then(signOff)
}
else if (task === 'docs') {
docs(parsedArgs).then(signOff)
}
else if (task === 'validate') {
validate(parsedArgs).then(signOff)
}
else if (task === 'openrpc') {
openrpc(parsedArgs).then(signOff)
} else {
console.log("Invalid build type")
}
8 changes: 7 additions & 1 deletion src/firebolt-openrpc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$id": "https://meta.comcast.com/firebolt/openrpc",
"$id": "https://meta.rdkcentral.com/firebolt/schemas/openrpc",
"title": "FireboltOpenRPC",
"oneOf": [
{
Expand Down Expand Up @@ -1012,6 +1012,12 @@
"x-provided-by": {
"type": "string"
},
"x-requestor": {
"type": "string"
},
"x-push": {
"type": "boolean"
},
"x-provider-selection": {
"type": "string",
"enum": [
Expand Down
2 changes: 1 addition & 1 deletion src/openrpc-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
{
"name": "rpc.discover",
"summary": "The OpenRPC schema for this JSON-RPC API",
"params": [],
"tags": [
{
"name": "capabilities",
"x-uses": [ "xrn:firebolt:capability:rpc:discover" ]
}
],
"params": [],
"result": {
"name": "OpenRPC Schema",
"schema": {
Expand Down
83 changes: 54 additions & 29 deletions src/openrpc/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,39 @@
*/

import { readJson, readFiles, readDir, writeJson } from "../shared/filesystem.mjs"
import { addExternalMarkdown, addExternalSchemas, fireboltize, fireboltizeMerged } from "../shared/modules.mjs"
import { addExternalMarkdown, addExternalSchemas, fireboltize } from "../shared/modules.mjs"
import path from "path"
import { logHeader, logSuccess } from "../shared/io.mjs"
import { namespaceRefs } from "../shared/json-schema.mjs"

const run = async ({
input: input,
output: output,
appApi: appApi,
platformApi: platformApi,
template: template,
schemas: schemas,
argv: {
remain: moduleWhitelist
}
}) => {

let openrpc = await readJson(template)
let platformApiOpenRpc = await readJson(template)
let appApiOpenRpc = appApi && await readJson(template)
let mergedOpenRpc = await readJson(template)

const sharedSchemaList = schemas ? (await Promise.all(schemas.map(d => readDir(d, { recursive: true })))).flat() : []
const sharedSchemas = await readFiles(sharedSchemaList)

try {
const packageJson = await readJson(path.join(input, '..', 'package.json'))
openrpc.info.version = packageJson.version
platformApiOpenRpc.info.version = packageJson.version
appApiOpenRpc && (appApiOpenRpc.info.version = packageJson.version)
}
catch (error) {
// fail silently
}

logHeader(`Generating compiled ${openrpc.info.title} OpenRPC document version ${openrpc.info.version}`)
logHeader(`Generating compiled ${platformApiOpenRpc.info.title} OpenRPC document version ${platformApiOpenRpc.info.version}`)

Object.entries(sharedSchemas).forEach(([path, schema]) => {
const json = JSON.parse(schema)
Expand All @@ -55,57 +64,60 @@ const run = async ({
const descriptionsList = input ? await readDir(path.join(input, 'descriptions'), { recursive: true }) : []
const markdown = await readFiles(descriptionsList, path.join(input, 'descriptions'))

Object.keys(modules).forEach(key => {
let json = JSON.parse(modules[key])
const isNotifier = method => method.tags.find(t => t.name === 'notifier')
const isProvider = method => method.tags.find(t => t.name === 'capabilities')['x-provides'] && !method.tags.find(t => t.name === 'event') && !method.tags.find(t => t.name === 'polymorphic-pull') && !method.tags.find(t => t.name === 'registration')

// Do the firebolt API magic
json = fireboltize(json)
const isAppApi = method => appApi && (isNotifier(method) || isProvider(method))
const isPlatformApi = method => !isAppApi(method)

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)

// put module name in front of each method
json.methods.forEach(method => method.name = method.name.includes('\.') ? method.name : json.info.title + '.' + method.name)
json.methods.filter(method => method.name.indexOf('.') === -1).forEach(method => method.name = json.info.title + '.' + method.name)

// merge any info['x-'] extension values (maps & arrays only..)
Object.keys(json.info).filter(key => key.startsWith('x-')).forEach(extension => {
if (Array.isArray(json.info[extension])) {
openrpc.info[extension] = openrpc.info[extension] || []
openrpc.info[extension].push(...json.info[extension])
mergedOpenRpc.info[extension] = mergedOpenRpc.info[extension] || []
mergedOpenRpc.info[extension].push(...json.info[extension])
}
else if (typeof json.info[extension] === 'object') {
openrpc.info[extension] = openrpc.info[extension] || {}
mergedOpenRpc.info[extension] = mergedOpenRpc.info[extension] || {}
Object.keys(json.info[extension]).forEach(k => {
openrpc.info[extension][k] = json.info[extension][k]
mergedOpenRpc.info[extension][k] = json.info[extension][k]
})
}
})

if (json.info.description) {
openrpc.info['x-module-descriptions'] = openrpc.info['x-module-descriptions'] || {}
openrpc.info['x-module-descriptions'][json.info.title] = json.info.description
mergedOpenRpc.info['x-module-descriptions'] = mergedOpenRpc.info['x-module-descriptions'] || {}
mergedOpenRpc.info['x-module-descriptions'][json.info.title] = json.info.description
}


// add methods from this module
openrpc.methods.push(...json.methods)
mergedOpenRpc.methods.push(...json.methods)

// add schemas from this module
json.components && Object.assign(openrpc.components.schemas, json.components.schemas)
// json.components && Object.assign(mergedOpenRpc.components.schemas, json.components.schemas)
json.components && json.components.schemas && Object.assign(mergedOpenRpc.components.schemas, Object.fromEntries(Object.entries(json.components.schemas).map( ([key, schema]) => ([json.info.title + '.' + key, schema]) )))
namespaceRefs('', json.info.title, mergedOpenRpc)

// add externally referenced schemas that are in our shared schemas path
openrpc = addExternalSchemas(openrpc, sharedSchemas)
mergedOpenRpc = addExternalSchemas(mergedOpenRpc, sharedSchemas)

modules[key] = JSON.stringify(json, null, '\t')

logSuccess(`Generated the ${json.info.title} module.`)
logSuccess(`Merged the ${json.info.title} module.`)
})

// Fireboltize!
mergedOpenRpc = fireboltize(mergedOpenRpc, !!appApi)

// make sure all provided-by APIs point to a real provider method
const appProvided = openrpc.methods.filter(m => m.tags.find(t=>t['x-provided-by'])) || []
const appProvided = mergedOpenRpc.methods.filter(m => m.tags.find(t=>t['x-provided-by'])) || []
appProvided.forEach(m => {
const providedBy = m.tags.find(t=>t['x-provided-by'])['x-provided-by']
const provider = openrpc.methods.find(m => m.name === providedBy)
const provider = mergedOpenRpc.methods.find(m => m.name === providedBy)
if (!provider) {
throw `Method ${m.name} is provided by an undefined method (${providedBy})`
}
Expand All @@ -114,12 +126,25 @@ const run = async ({
}
})

openrpc = fireboltizeMerged(openrpc)
Object.assign(platformApiOpenRpc.info, mergedOpenRpc.info)

// Split into platformApi & appApi
platformApiOpenRpc.methods.push(...mergedOpenRpc.methods.filter(isPlatformApi))
appApiOpenRpc && appApiOpenRpc.methods.push(...mergedOpenRpc.methods.filter(isAppApi))

// Add schemas
mergedOpenRpc.components && Object.assign(platformApiOpenRpc.components.schemas, mergedOpenRpc.components.schemas)
appApiOpenRpc?.components && Object.assign(appApiOpenRpc.components.schemas, mergedOpenRpc.components.schemas)

// Add externally referenced schemas that are in our shared schemas path
platformApiOpenRpc = addExternalSchemas(platformApiOpenRpc, sharedSchemas)
appApiOpenRpc && (appApiOpenRpc = addExternalSchemas(appApiOpenRpc, sharedSchemas))

await writeJson(output, openrpc)
await writeJson(platformApi, platformApiOpenRpc)
appApiOpenRpc && await writeJson(appApi, appApiOpenRpc)

console.log()
logSuccess(`Wrote file ${path.relative('.', output)}`)
logSuccess(`Wrote file ${path.relative('.', platformApi)}`)
appApi && logSuccess(`Wrote file ${path.relative('.', appApi)}`)

return Promise.resolve()
}
Expand Down
Loading
Loading