diff --git a/CHANGELOG.md b/CHANGELOG.md index 78b821d4..73501df5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# [2.3.0-next.1](https://github.com/rdkcentral/firebolt-openrpc/compare/v2.2.0...v2.3.0-next.1) (2024-01-12) + + +### Bug Fixes + +* Insert 'v' in front of version enum names ([d8b9ada](https://github.com/rdkcentral/firebolt-openrpc/commit/d8b9ada1b624df29821b74679291d36167208470)) +* Proper param handling for x-subscriber-type: global ([02204e5](https://github.com/rdkcentral/firebolt-openrpc/commit/02204e5f93d27a21086b4a9bbe5f586f969354f8)) + + +### Features + +* Support for context-free property subscribers ([9809273](https://github.com/rdkcentral/firebolt-openrpc/commit/980927309fa6efc7b03a490aa5fd7909f39ff4de)) + # [2.2.0](https://github.com/rdkcentral/firebolt-openrpc/compare/v2.1.2...v2.2.0) (2023-11-30) diff --git a/languages/c/Types.mjs b/languages/c/Types.mjs index 1ff0fdf9..35c46ba2 100644 --- a/languages/c/Types.mjs +++ b/languages/c/Types.mjs @@ -17,7 +17,7 @@ */ import deepmerge from 'deepmerge' -import { getPath } from '../../src/shared/json-schema.mjs' +import { getPath, getSafeEnumKeyName } from '../../src/shared/json-schema.mjs' import { getTypeName, getModuleName, description, getObjectManagement, getNativeType, getPropertyAccessors, capitalize, isOptional, generateEnum, getMapAccessors, getArrayAccessors, getPropertyGetterSignature, getFireboltStringType } from './src/types/NativeHelpers.mjs' import { getArrayAccessorsImpl, getMapAccessorsImpl, getObjectManagementImpl, getParameterInstantiation, getPropertyAccessorsImpl, getResultInstantiation, getCallbackParametersInstantiation, getCallbackResultInstantiation, getCallbackResponseInstantiation } from './src/types/ImplHelpers.mjs' import { getJsonContainerDefinition, getJsonDataStructName, getJsonDataPrefix } from './src/types/JSONHelpers.mjs' @@ -717,24 +717,6 @@ function getJsonTypeInfo(module = {}, json = {}, name = '', schemas, prefix = '' return structure } -function getTypeScriptType(jsonType) { - if (jsonType === 'integer') { - return 'number' - } - else { - return jsonType - } -} - -const enumReducer = (acc, val, i, arr) => { - const keyName = val.split(':').pop().replace(/[\.\-]/g, '_').replace(/\+/g, '_plus').replace(/([a-z])([A-Z0-9])/g, '$1_$2').toUpperCase() - acc = acc + ` ${keyName} = '${val}'` - if (i < arr.length - 1) { - acc = acc.concat(',\n') - } - return acc -} - function getSchemaInstantiation(schema, module, name, { instantiationType = '', prefix = '' } = {}) { if (instantiationType === 'params') { diff --git a/languages/c/src/types/NativeHelpers.mjs b/languages/c/src/types/NativeHelpers.mjs index d2540279..061f30b8 100644 --- a/languages/c/src/types/NativeHelpers.mjs +++ b/languages/c/src/types/NativeHelpers.mjs @@ -22,7 +22,7 @@ import safe from 'crocks/Maybe/safe.js' import pointfree from 'crocks/pointfree/index.js' const { chain, filter, reduce, option, map } = pointfree import predicates from 'crocks/predicates/index.js' -import { getPath, getExternalSchemaPaths } from '../../../../src/shared/json-schema.mjs' +import { getPath, getExternalSchemaPaths, getSafeEnumKeyName } from '../../../../src/shared/json-schema.mjs' import deepmerge from 'deepmerge' const { isObject, isArray, propEq, pathSatisfies, hasProp, propSatisfies } = predicates @@ -126,7 +126,7 @@ const getArrayAccessors = (arrayName, propertyType, valueType) => { } const enumValue = (val,prefix) => { - const keyName = val.replace(/[\.\-:]/g, '_').replace(/\+/g, '_plus').replace(/([a-z])([A-Z0-9])/g, '$1_$2').toUpperCase() + const keyName = getSafeEnumKeyName(val) return ` ${prefix.toUpperCase()}_${keyName.toUpperCase()}` } diff --git a/languages/javascript/src/shared/Prop/Router.mjs b/languages/javascript/src/shared/Prop/Router.mjs index f4462f6c..37ca4c55 100644 --- a/languages/javascript/src/shared/Prop/Router.mjs +++ b/languages/javascript/src/shared/Prop/Router.mjs @@ -7,6 +7,9 @@ export default function (params, callbackOrValue, contextParameterCount) { } else if (numArgs === contextParameterCount && typeof callbackOrValue === 'function') { // subscribe return "subscriber" + } else if (numArgs === 0 && typeof callbackOrValue === 'function') { + // for x-subscriber-type: global + return "subscriber" } else if (numArgs === (contextParameterCount) && callbackOrValue !== undefined) { // setter return "setter" diff --git a/languages/javascript/templates/declarations/property.js b/languages/javascript/templates/declarations/property.js index 138ba01a..85d61375 100644 --- a/languages/javascript/templates/declarations/property.js +++ b/languages/javascript/templates/declarations/property.js @@ -3,7 +3,7 @@ * ${method.params.annotations}${if.deprecated} * @deprecated ${method.deprecation} ${end.if.deprecated} */ - function ${method.name}(): Promise<${method.result.type}> + function ${method.name}(${method.signature.params}): Promise<${method.result.type}> ${method.setter} diff --git a/languages/javascript/templates/methods/property.js b/languages/javascript/templates/methods/property.js index 0b1e1359..49728392 100644 --- a/languages/javascript/templates/methods/property.js +++ b/languages/javascript/templates/methods/property.js @@ -1,4 +1,12 @@ function ${method.name}(${method.params.list}) { - const callbackOrValue = arguments[${method.params.count}] - return Prop.prop('${info.title}', '${method.name}', { ${method.params.list} }, callbackOrValue, ${method.property.immutable}, ${method.property.readonly}, ${method.params.count}) + let callbackOrValue = arguments[${method.params.count}] + let params = { ${method.params.list} } + + // x-subscriber-type: global + if (arguments.length === 1 && (typeof arguments[0] === 'function')) { + callbackOrValue = arguments[0] + params = {} + } + + return Prop.prop('${info.title}', '${method.name}', params, callbackOrValue, ${method.property.immutable}, ${method.property.readonly}, ${method.params.count}) } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5dad0a1f..475958e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@firebolt-js/openrpc", - "version": "2.2.0", + "version": "2.3.0-next.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@firebolt-js/openrpc", - "version": "2.2.0", + "version": "2.3.0-next.1", "license": "Apache-2.0", "dependencies": { "ajv": "^8.3.0", diff --git a/package.json b/package.json index 0daee911..cafd1855 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@firebolt-js/openrpc", - "version": "2.2.0", + "version": "2.3.0-next.1", "description": "The Firebolt SDK Code & Doc Generator", "main": "languages/javascript/src/sdk.mjs", "type": "module", diff --git a/src/macrofier/engine.mjs b/src/macrofier/engine.mjs index 543e9a57..3f7596ec 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, isCallsMetricsMethod, isExcludedMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs, createPolymorphicMethods } from '../shared/modules.mjs' import isEmpty from 'crocks/core/isEmpty.js' -import { getLinkedSchemaPaths, getSchemaConstraints, isSchema, localizeDependencies, isDefinitionReferencedBySchema } from '../shared/json-schema.mjs' +import { getLinkedSchemaPaths, getSchemaConstraints, isSchema, localizeDependencies, isDefinitionReferencedBySchema, getSafeEnumKeyName } from '../shared/json-schema.mjs' // util for visually debugging crocks ADTs const _inspector = obj => { @@ -634,7 +634,7 @@ const convertEnumTemplate = (schema, templateName, templates) => { for (var i = 0; i < template.length; i++) { if (template[i].indexOf('${key}') >= 0) { template[i] = enumSchema.enum.map(value => { - const safeName = value.split(':').pop().replace(/[\.\-]/g, '_').replace(/\+/g, '_plus').replace(/([a-z])([A-Z0-9])/g, '$1_$2').toUpperCase() + const safeName = getSafeEnumKeyName(value) return template[i].replace(/\$\{key\}/g, safeName) .replace(/\$\{value\}/g, value) }).join('\n') diff --git a/src/shared/json-schema.mjs b/src/shared/json-schema.mjs index e7bb1d86..76a5739f 100644 --- a/src/shared/json-schema.mjs +++ b/src/shared/json-schema.mjs @@ -355,8 +355,16 @@ const isDefinitionReferencedBySchema = (name = '', moduleJson = {}) => { return (refs.length > 0) } +const getSafeEnumKeyName = (value) => value.split(':').pop() // use last portion of urn:style:values + .replace(/[\.\-]/g, '_') // replace dots and dashes + .replace(/\+/g, '_plus') // change + to _plus + .replace(/([a-z])([A-Z0-9])/g, '$1_$2') // camel -> snake case + .replace(/^([0-9]+(\.[0-9]+)?)/, 'v$1') // insert `v` in front of things that look like version numbers + .toUpperCase() + export { getSchemaConstraints, + getSafeEnumKeyName, getExternalSchemaPaths, getLocalSchemas, getLocalSchemaPaths, diff --git a/src/shared/modules.mjs b/src/shared/modules.mjs index f1efc1bc..095acd87 100644 --- a/src/shared/modules.mjs +++ b/src/shared/modules.mjs @@ -376,6 +376,35 @@ const eventDefaults = event => { return event } +const createEventResultSchemaFromProperty = property => { + const subscriberType = property.tags.map(t => t['x-subscriber-type']).find(t => typeof t === 'string') || 'context' + + if (property.tags.find(t => (t.name == 'property' || t.name.startsWith('property:')) && (subscriberType === 'global'))) { + // wrap the existing result and the params in a new result object + const schema = { + title: property.name.charAt(0).toUpperCase() + property.name.substring(1) + 'ChangedInfo', + type: "object", + properties: { + + }, + required: [] + } + + // add all of the params + property.params.filter(p => p.name !== 'listen').forEach(p => { + schema.properties[p.name] = p.schema + schema.required.push(p.name) + }) + + // add the result (which might override a param of the same name) + schema.properties[property.result.name] = property.result.schema + !schema.required.includes(property.result.name) && schema.required.push(property.result.name) + + + return schema + } +} + const createEventFromProperty = property => { const event = eventDefaults(JSON.parse(JSON.stringify(property))) event.name = 'on' + event.name.charAt(0).toUpperCase() + event.name.substr(1) + 'Changed' @@ -388,6 +417,36 @@ const createEventFromProperty = property => { 'x-subscriber-for': property.name }) + const subscriberType = property.tags.map(t => t['x-subscriber-type']).find(t => typeof t === 'string') || 'context' + + // if the subscriber type is global, zap all of the parameters and change the result type to the schema that includes them + if (old_tags.find(t => (t.name == 'property' || t.name.startsWith('property:')) && (subscriberType === 'global'))) { + + // wrap the existing result and the params in a new result object + const result = { + name: "data", + schema: { + $ref: "#/components/schemas/" + event.name.substring(2) + 'Info' + } + } + + event.examples.map(example => { + const result = {} + example.params.filter(p => p.name !== 'listen').forEach(p => { + result[p.name] = p.value + }) + result[example.result.name] = example.result.value + example.params = example.params.filter(p => p.name === 'listen') + example.result.name = "data" + example.result.value = result + }) + + event.result = result + + // remove the params + event.params = event.params.filter(p => p.name === 'listen') + } + old_tags.forEach(t => { if (t.name !== 'property' && !t.name.startsWith('property:')) { @@ -750,8 +809,20 @@ const generatePropertyEvents = json => { const properties = json.methods.filter( m => m.tags && m.tags.find( t => t.name == 'property')) || [] const readonlies = json.methods.filter( m => m.tags && m.tags.find( t => t.name == 'property:readonly')) || [] - properties.forEach(property => json.methods.push(createEventFromProperty(property))) - readonlies.forEach(property => json.methods.push(createEventFromProperty(property))) + properties.forEach(property => { + json.methods.push(createEventFromProperty(property)) + const schema = createEventResultSchemaFromProperty(property) + if (schema) { + json.components.schemas[schema.title] = schema + } + }) + readonlies.forEach(property => { + json.methods.push(createEventFromProperty(property)) + const schema = createEventResultSchemaFromProperty(property) + if (schema) { + json.components.schemas[schema.title] = schema + } + }) return json } diff --git a/src/shared/typescript.mjs b/src/shared/typescript.mjs index 5982137e..28e80086 100644 --- a/src/shared/typescript.mjs +++ b/src/shared/typescript.mjs @@ -17,7 +17,7 @@ */ import deepmerge from 'deepmerge' -import { getPath, localizeDependencies } from './json-schema.mjs' +import { getPath, getSafeEnumKeyName, localizeDependencies } from './json-schema.mjs' const isSynchronous = m => !m.tags ? false : m.tags.map(t => t.name).find(s => s === 'synchronous') @@ -45,7 +45,7 @@ function getSchemaShape(schema = {}, module = {}, { name = '', level = 0, title, let theTitle = (level === 0 ? schema.title || name : name) if (enums && level === 0 && schema.type === "string" && Array.isArray(schema.enum)) { - return `enum ${schema.title || name} {\n\t` + schema.enum.map(value => value.split(':').pop().replace(/[\.\-]/g, '_').replace(/\+/g, '_plus').replace(/([a-z])([A-Z0-9])/g, '$1_$2').toUpperCase() + ` = '${value}'`).join(',\n\t') + '\n}\n' + return `enum ${schema.title || name} {\n\t` + schema.enum.map(value => getSafeEnumKeyName(value) + ` = '${value}'`).join(',\n\t') + '\n}\n' } if (!theTitle) { @@ -338,7 +338,7 @@ function getSchemaShape(schema = {}, module = {}, { name = '', level = 0, title, } const enumReducer = (acc, val, i, arr) => { - const keyName = val.split(':').pop().replace(/[\.\-]/g, '_').replace(/\+/g, '_plus').replace(/([a-z])([A-Z0-9])/g, '$1_$2').toUpperCase() + const keyName = getSafeEnumKeyName(val) acc = acc + ` ${keyName} = '${val}'` if (i < arr.length-1) { acc = acc.concat(',\n')