From 33db98ca5f6a4179c42b3693c96b797210e85a67 Mon Sep 17 00:00:00 2001 From: Jeremy LaCivita Date: Thu, 31 Aug 2023 10:46:00 -0400 Subject: [PATCH] feat: C Language Support (#130) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: Copy JS templates to C and create language.config.json * feat(languages): Add support for a distinct JSON-type for each schema Also started tweaking C templates and adding a few useful macros. * chore: Dropped debug logs * fix: Stop passing non-schemas in to getSchemaType * feat(accessors): New macro section for schema property accessors * fix: Sort the macrofied schemas based on schema refs (#90) * fix: Sort the macrofied schemas based on schema refs * fix: Sort the schemas before macrofying * fix: Handle the schemas part * Enum fixes (#93) * Types and Accessor related fixes (#94) * Static code implementation updates (#96) * Add support to generate file inclusion for common schema also (#97) * C-Language: Templates based property methods (#100) * feat: Add method templates for properties * feat: Property setters using templates * fix: Macro name correction * Add support to keep original file permissions of template/sdk files (#99) * Event template implementation added (#101) * Template for Polymorphic pull method (#102) * feat: Add method templates for properties * feat: Property setters using templates * fix: Macro name correction * feat: Add template for polymorphic-pull * fix: Promote and name sub-schemas in one place * fix: Add extractSubSchemas flag to C language * fix: Uncomment writeFilePermissions * polymorphic-pull: method and event implementation added (#107) * SchemaSorting: logic updated to consider all schemas (#106) * fix: Fix the reentrancy of methods from callback (#105) * Default template implementation added (#103) Default template implementation added * Naming + void* to struct* changes based on sky review, removed redundant code, array handling fix (#109) Naming + void* to struct* changes based on sky review, removed redundant code, array handling fix * language-c: re-arrange files locations (#112) language-c: re-arrange files locations * fixes to generate array inside subschemas (#111) * include test always (#113) * use String type if there is any issue with anyOf merge (#114) * calls-metrics: support added (#115) * Generate polymorphci methods for anyOf param/result case (#110) * Create namespaces only if there is value to put (#117) * Changes to avoid duplication of sub-array schema with same reference (#116) * detach setter declaration from property template and add setter templ… (#118) Detach setter declaration from property template and add setter template, remove unredundant warnings Cleanup changes, add separator between prefix and subSchema name generation Introduce excludeDeclarations flag to handle declaration exclusion specific to language Alignement changes + cleanup + OUT params support added to differentiate method signature parameters Update in static code * SubArray generation issue fix (#121) * cmake changes to install proper files (#119) * chore: Copy JS templates to C and create language.config.json * feat(languages): Add support for a distinct JSON-type for each schema Also started tweaking C templates and adding a few useful macros. * chore: Dropped debug logs * fix: Stop passing non-schemas in to getSchemaType * feat(accessors): New macro section for schema property accessors * fix: Sort the macrofied schemas based on schema refs (#90) * fix: Sort the macrofied schemas based on schema refs * fix: Sort the schemas before macrofying * fix: Handle the schemas part * Enum fixes (#93) Enum fixes : 1. missing description added 2. alignment fixes 3. implementation added for enum inside properties 4. Enum to String value Conversion logic added for C * Types and Accessor related fixes (#94) Types and Accessor related fixes: 1. Accessor creation 2. JsonContainer creation 3. Types order placed based on depenedencies 4. filtered redundant new lines 5. UnamedSchema issue fixes 6. Types and Accessors: generation added for objects inside methods result/params 7. AnyOf support added * Static code implementation updates (#96) Static code implementation updates Co-authored-by: Jeremy LaCivita * Add support to generate file inclusion for common schema also (#97) Add support to generate file inclusion for common schema also * C-Language: Templates based property methods (#100) * feat: Add method templates for properties * feat: Property setters using templates * fix: Macro name correction * Add support to keep original file permissions of template/sdk files (#99) * Event template implementation added (#101) * Template for Polymorphic pull method (#102) * feat: Add method templates for properties * feat: Property setters using templates * fix: Macro name correction * feat: Add template for polymorphic-pull * fix: Promote and name sub-schemas in one place * fix: Add extractSubSchemas flag to C language * fix: Uncomment writeFilePermissions * polymorphic-pull: method and event implementation added (#107) * SchemaSorting: logic updated to consider all schemas (#106) * fix: Fix the reentrancy of methods from callback (#105) * Default template implementation added (#103) Default template implementation added * Naming + void* to struct* changes based on sky review, removed redundant code, array handling fix (#109) Naming + void* to struct* changes based on sky review, removed redundant code, array handling fix * language-c: re-arrange files locations (#112) language-c: re-arrange files locations * fixes to generate array inside subschemas (#111) * include test always (#113) * use String type if there is any issue with anyOf merge (#114) * calls-metrics: support added (#115) * Generate polymorphci methods for anyOf param/result case (#110) * Create namespaces only if there is value to put (#117) * Changes to avoid duplication of sub-array schema with same reference (#116) * detach setter declaration from property template and add setter templ… (#118) Detach setter declaration from property template and add setter template, remove unredundant warnings Cleanup changes, add separator between prefix and subSchema name generation Introduce excludeDeclarations flag to handle declaration exclusion specific to language Alignement changes + cleanup + OUT params support added to differentiate method signature parameters Update in static code * SubArray generation issue fix (#121) * cmake changes to install proper files (#119) * native code: changed return type from uint32_t to int32_t (#127) --------- Co-authored-by: sramani-metro <71630728+sramani-metro@users.noreply.github.com> Co-authored-by: HaseenaSainul <41037131+HaseenaSainul@users.noreply.github.com> --- languages/c/Types.mjs | 789 +++++++++++++++ languages/c/language.config.json | 18 + languages/c/src/index.mjs | 19 + languages/c/src/shared/CMakeLists.txt | 56 ++ .../c/src/shared/cmake/HelperFunctions.cmake | 72 ++ languages/c/src/shared/cmake/project.cmake.in | 35 + languages/c/src/shared/include/error.h | 41 + languages/c/src/shared/include/firebolt.h | 68 ++ languages/c/src/shared/include/types.h | 37 + .../c/src/shared/src/Accessor/Accessor.cpp | 117 +++ .../c/src/shared/src/Accessor/Accessor.h | 122 +++ .../c/src/shared/src/Accessor/WorkerPool.h | 102 ++ languages/c/src/shared/src/CMakeLists.txt | 73 ++ languages/c/src/shared/src/Event/Event.cpp | 147 +++ languages/c/src/shared/src/Event/Event.h | 165 ++++ .../c/src/shared/src/FireboltSDK.conf.in | 3 + languages/c/src/shared/src/FireboltSDK.h | 26 + languages/c/src/shared/src/Logger/Logger.cpp | 86 ++ languages/c/src/shared/src/Logger/Logger.h | 85 ++ languages/c/src/shared/src/Module.cpp | 21 + languages/c/src/shared/src/Module.h | 29 + .../c/src/shared/src/Properties/Properties.h | 148 +++ .../c/src/shared/src/Transport/Transport.cpp | 24 + .../c/src/shared/src/Transport/Transport.h | 897 ++++++++++++++++++ languages/c/src/shared/src/Types.cpp | 40 + languages/c/src/shared/src/TypesPriv.h | 56 ++ languages/c/src/shared/src/firebolt.cpp | 39 + languages/c/src/shared/test/CMakeLists.txt | 86 ++ languages/c/src/shared/test/Main.c | 45 + languages/c/src/shared/test/Module.cpp | 21 + languages/c/src/shared/test/Module.h | 28 + languages/c/src/shared/test/OpenRPCCTests.h | 43 + languages/c/src/shared/test/OpenRPCTests.cpp | 504 ++++++++++ languages/c/src/shared/test/OpenRPCTests.h | 118 +++ languages/c/src/shared/test/TestUtils.h | 38 + languages/c/src/types/ImplHelpers.mjs | 510 ++++++++++ languages/c/src/types/JSONHelpers.mjs | 57 ++ languages/c/src/types/NativeHelpers.mjs | 172 ++++ languages/c/templates/codeblocks/export.c | 0 .../c/templates/codeblocks/mock-import.c | 1 + .../c/templates/codeblocks/mock-parameter.c | 1 + languages/c/templates/codeblocks/module.c | 0 languages/c/templates/codeblocks/setter.c | 7 + languages/c/templates/declarations/clear.c | 0 languages/c/templates/declarations/default.c | 4 + languages/c/templates/declarations/event.c | 5 + languages/c/templates/declarations/listen.c | 0 languages/c/templates/declarations/once.c | 0 .../declarations/polymorphic-pull-event.c | 5 + .../templates/declarations/polymorphic-pull.c | 3 + .../declarations/polymorphic-reducer.c | 7 + languages/c/templates/declarations/property.c | 5 + languages/c/templates/declarations/provide.c | 0 languages/c/templates/declarations/setter.c | 2 + languages/c/templates/defaults/default.c | 0 languages/c/templates/defaults/property.c | 0 .../c/templates/imports/calls-metrics.cpp | 1 + languages/c/templates/imports/default.cpp | 1 + languages/c/templates/imports/default.h | 1 + .../c/templates/imports/default.jsondata | 1 + languages/c/templates/methods/calls-metrics.c | 30 + languages/c/templates/methods/clear.c | 0 languages/c/templates/methods/default.c | 22 + languages/c/templates/methods/event.c | 27 + languages/c/templates/methods/listen.c | 0 languages/c/templates/methods/once.c | 0 .../methods/polymorphic-pull-event.c | 54 ++ .../c/templates/methods/polymorphic-pull.c | 23 + .../c/templates/methods/polymorphic-reducer.c | 0 languages/c/templates/methods/property.c | 14 + languages/c/templates/methods/provide.c | 0 languages/c/templates/methods/setter.c | 0 .../c/templates/modules/include/module.h | 44 + languages/c/templates/modules/src/module.cpp | 49 + languages/c/templates/parameters/default.c | 1 + languages/c/templates/parameters/json.c | 3 + languages/c/templates/schemas/default.c | 1 + .../templates/schemas/include/common/module.h | 39 + .../c/templates/schemas/src/jsondata_module.h | 30 + .../c/templates/schemas/src/module_common.cpp | 42 + languages/c/templates/sdk/scripts/build.sh | 40 + languages/c/templates/sdk/scripts/install.sh | 58 ++ languages/c/templates/sections/accessors.c | 1 + languages/c/templates/sections/declarations.c | 1 + languages/c/templates/sections/enum.cpp | 5 + languages/c/templates/sections/events.c | 1 + languages/c/templates/sections/methods.c | 1 + .../c/templates/sections/methods_accessors.c | 1 + .../c/templates/sections/methods_types.c | 1 + .../templates/sections/provider-interfaces.c | 11 + languages/c/templates/sections/schemas.c | 1 + languages/c/templates/sections/types.c | 1 + languages/c/templates/types/enum.cpp | 4 + languages/c/templates/types/enum.h | 4 + .../javascript/templates/methods/property.js | 2 +- src/macrofier/engine.mjs | 783 +++++++++------ src/macrofier/index.mjs | 43 +- src/sdk/index.mjs | 6 +- src/shared/filesystem.mjs | 63 +- src/shared/modules.mjs | 145 ++- src/shared/typescript.mjs | 17 +- 101 files changed, 6237 insertions(+), 312 deletions(-) create mode 100644 languages/c/Types.mjs create mode 100644 languages/c/language.config.json create mode 100644 languages/c/src/index.mjs create mode 100644 languages/c/src/shared/CMakeLists.txt create mode 100644 languages/c/src/shared/cmake/HelperFunctions.cmake create mode 100644 languages/c/src/shared/cmake/project.cmake.in create mode 100644 languages/c/src/shared/include/error.h create mode 100644 languages/c/src/shared/include/firebolt.h create mode 100644 languages/c/src/shared/include/types.h create mode 100644 languages/c/src/shared/src/Accessor/Accessor.cpp create mode 100644 languages/c/src/shared/src/Accessor/Accessor.h create mode 100644 languages/c/src/shared/src/Accessor/WorkerPool.h create mode 100644 languages/c/src/shared/src/CMakeLists.txt create mode 100644 languages/c/src/shared/src/Event/Event.cpp create mode 100644 languages/c/src/shared/src/Event/Event.h create mode 100644 languages/c/src/shared/src/FireboltSDK.conf.in create mode 100644 languages/c/src/shared/src/FireboltSDK.h create mode 100644 languages/c/src/shared/src/Logger/Logger.cpp create mode 100644 languages/c/src/shared/src/Logger/Logger.h create mode 100644 languages/c/src/shared/src/Module.cpp create mode 100644 languages/c/src/shared/src/Module.h create mode 100644 languages/c/src/shared/src/Properties/Properties.h create mode 100644 languages/c/src/shared/src/Transport/Transport.cpp create mode 100644 languages/c/src/shared/src/Transport/Transport.h create mode 100644 languages/c/src/shared/src/Types.cpp create mode 100644 languages/c/src/shared/src/TypesPriv.h create mode 100644 languages/c/src/shared/src/firebolt.cpp create mode 100644 languages/c/src/shared/test/CMakeLists.txt create mode 100644 languages/c/src/shared/test/Main.c create mode 100644 languages/c/src/shared/test/Module.cpp create mode 100644 languages/c/src/shared/test/Module.h create mode 100644 languages/c/src/shared/test/OpenRPCCTests.h create mode 100644 languages/c/src/shared/test/OpenRPCTests.cpp create mode 100644 languages/c/src/shared/test/OpenRPCTests.h create mode 100644 languages/c/src/shared/test/TestUtils.h create mode 100644 languages/c/src/types/ImplHelpers.mjs create mode 100644 languages/c/src/types/JSONHelpers.mjs create mode 100644 languages/c/src/types/NativeHelpers.mjs create mode 100644 languages/c/templates/codeblocks/export.c create mode 100644 languages/c/templates/codeblocks/mock-import.c create mode 100644 languages/c/templates/codeblocks/mock-parameter.c create mode 100644 languages/c/templates/codeblocks/module.c create mode 100644 languages/c/templates/codeblocks/setter.c create mode 100644 languages/c/templates/declarations/clear.c create mode 100644 languages/c/templates/declarations/default.c create mode 100644 languages/c/templates/declarations/event.c create mode 100644 languages/c/templates/declarations/listen.c create mode 100644 languages/c/templates/declarations/once.c create mode 100644 languages/c/templates/declarations/polymorphic-pull-event.c create mode 100644 languages/c/templates/declarations/polymorphic-pull.c create mode 100644 languages/c/templates/declarations/polymorphic-reducer.c create mode 100644 languages/c/templates/declarations/property.c create mode 100644 languages/c/templates/declarations/provide.c create mode 100644 languages/c/templates/declarations/setter.c create mode 100644 languages/c/templates/defaults/default.c create mode 100644 languages/c/templates/defaults/property.c create mode 100644 languages/c/templates/imports/calls-metrics.cpp create mode 100644 languages/c/templates/imports/default.cpp create mode 100644 languages/c/templates/imports/default.h create mode 100644 languages/c/templates/imports/default.jsondata create mode 100644 languages/c/templates/methods/calls-metrics.c create mode 100644 languages/c/templates/methods/clear.c create mode 100644 languages/c/templates/methods/default.c create mode 100644 languages/c/templates/methods/event.c create mode 100644 languages/c/templates/methods/listen.c create mode 100644 languages/c/templates/methods/once.c create mode 100644 languages/c/templates/methods/polymorphic-pull-event.c create mode 100644 languages/c/templates/methods/polymorphic-pull.c create mode 100644 languages/c/templates/methods/polymorphic-reducer.c create mode 100644 languages/c/templates/methods/property.c create mode 100644 languages/c/templates/methods/provide.c create mode 100644 languages/c/templates/methods/setter.c create mode 100644 languages/c/templates/modules/include/module.h create mode 100644 languages/c/templates/modules/src/module.cpp create mode 100644 languages/c/templates/parameters/default.c create mode 100644 languages/c/templates/parameters/json.c create mode 100644 languages/c/templates/schemas/default.c create mode 100644 languages/c/templates/schemas/include/common/module.h create mode 100644 languages/c/templates/schemas/src/jsondata_module.h create mode 100644 languages/c/templates/schemas/src/module_common.cpp create mode 100755 languages/c/templates/sdk/scripts/build.sh create mode 100755 languages/c/templates/sdk/scripts/install.sh create mode 100644 languages/c/templates/sections/accessors.c create mode 100644 languages/c/templates/sections/declarations.c create mode 100644 languages/c/templates/sections/enum.cpp create mode 100644 languages/c/templates/sections/events.c create mode 100644 languages/c/templates/sections/methods.c create mode 100644 languages/c/templates/sections/methods_accessors.c create mode 100644 languages/c/templates/sections/methods_types.c create mode 100644 languages/c/templates/sections/provider-interfaces.c create mode 100644 languages/c/templates/sections/schemas.c create mode 100644 languages/c/templates/sections/types.c create mode 100644 languages/c/templates/types/enum.cpp create mode 100644 languages/c/templates/types/enum.h diff --git a/languages/c/Types.mjs b/languages/c/Types.mjs new file mode 100644 index 00000000..8b14f4c3 --- /dev/null +++ b/languages/c/Types.mjs @@ -0,0 +1,789 @@ +/* + * 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 + */ + +import deepmerge from 'deepmerge' +import { getPath } 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' + +const getSdkNameSpace = () => 'FireboltSDK' +const getJsonNativeTypeForOpaqueString = () => getSdkNameSpace() + '::JSON::String' +const getEnumName = (name, prefix) => ((prefix.length > 0) ? (prefix + '_' + name) : name) + +const getRefModule = (title) => { + let module = { + info: { + title: `${title}` + } + } + return module +} + +const hasProperties = (prop) => { + let hasProperty = false + if (prop.properties) { + hasProperty = true + } else if (prop.additionalProperties && ( prop.additionalProperties.type && (((prop.additionalProperties.type === 'object') && prop.additionalProperties.properties) || (prop.additionalProperties.type !== 'object')))) { + hasProperty = true + } + return hasProperty +} + +function validJsonObjectProperties(json = {}) { + + let valid = true + if (json.type === 'object' || (json.additonalProperties && typeof json.additonalProperties.type === 'object')) { + if (json.properties || json.additonalProperties) { + Object.entries(json.properties || json.additonalProperties).every(([pname, prop]) => { + if (!prop['$ref'] && (pname !== 'additionalProperties') && + ((!prop.type && !prop.const && (prop.schema && !prop.schema.type)) || (Array.isArray(prop.type) && (prop.type.find(t => t === 'null'))))) { + valid = false + } + return valid + }) + } + } + return valid +} + +function union(schemas, module, commonSchemas) { + + const result = {}; + for (const schema of schemas) { + for (const [key, value] of Object.entries(schema)) { + if (!result.hasOwnProperty(key)) { + // If the key does not already exist in the result schema, add it + if (value && value.anyOf) { + result[key] = union(value.anyOf, module, commonSchemas) + } else if (key === 'title' || key === 'description' || key === 'required') { + //console.warn(`Ignoring "${key}"`) + } else { + result[key] = value; + } + } else if (key === 'type') { + // If the key is 'type', merge the types of the two schemas + if(result[key] === value) { + //console.warn(`Ignoring "${key}" that is already present and same`) + } else { + console.warn(`ERROR "${key}" is not same -${JSON.stringify(result, null, 4)} ${key} ${result[key]} - ${value}`); + return {} + } + } else { + //If the Key is a const then merge them into an enum + if(value && value.const) { + if(result[key].enum) { + result[key].enum = Array.from(new Set([...result[key].enum, value.const])) + } + else { + result[key].enum = Array.from(new Set([result[key].const, value.const])) + delete result[key].const + } + } + // If the key exists in both schemas and is not 'type', merge the values + else if (Array.isArray(result[key])) { + // If the value is an array, concatenate the arrays and remove duplicates + result[key] = Array.from(new Set([...result[key], ...value])) + } else if (result[key] && result[key].enum && value && value.enum) { + //If the value is an enum, merge the enums together and remove duplicates + result[key].enum = Array.from(new Set([...result[key].enum, ...value.enum])) + } else if (typeof result[key] === 'object' && typeof value === 'object') { + // If the value is an object, recursively merge the objects + result[key] = union([result[key], value], module, commonSchemas); + } else if (result[key] !== value) { + // If the value is a primitive and is not the same in both schemas, ignore it + //console.warn(`Ignoring conflicting value for key "${key}"`) + } + } + } + } + return result; +} + +function getMergedSchema(module, json, name, schemas) { + let refsResolved = [...json.anyOf.map(x => x['$ref'] ? getPath(x['$ref'], module, schemas) || x : x)] + let allOfsResolved = refsResolved.map(sch => sch.allOf ? deepmerge.all([...sch.allOf.map(x => x['$ref'] ? getPath(x['$ref'], module, schemas) || x : x)]) : sch) + + let mergedSchema = union(allOfsResolved, module, schemas) + if (json.title) { + mergedSchema['title'] = json.title + } + else { + mergedSchema['title'] = name + } + + delete mergedSchema['$ref'] + return mergedSchema +} + +const deepMergeAll = (module, name, schema, schemas, options) => { + let nonRefsProperty = [...schema.allOf.map(x => x['$ref'] ? '' : x)].filter(elm => elm) + let refsProperty = [...schema.allOf.map(x => x['$ref'] ? getPath(x['$ref'], module, schemas) : '')].filter(elm => elm) + let mergedProperty = [] + let mergedParamSchema = { + type: "object", + properties: {} + } + + nonRefsProperty.forEach(p => { + if (p.properties) { + Object.entries(p.properties).every(([pname, prop]) => { + let present = false + refsProperty.forEach(refP => { + if (refP.properties) { + Object.entries(refP.properties).every(([refname, refprop]) => { + if (refname == pname) { + present = true + } + return !present + }) + } + }) + let prefixedName = (present == false) ? (name + capitalize(pname)) : pname + mergedParamSchema.properties[prefixedName] = prop + return true + }) + mergedProperty.push(mergedParamSchema) + } + }) + refsProperty.forEach(ref => mergedProperty.push(ref)) + let union = deepmerge.all(mergedProperty) + + return union +} +const hasTag = (method, tag) => { + return method.tags && method.tags.filter(t => t.name === tag).length > 0 +} + +const IsResultConstNullSuccess = (schema, name) => (name === 'success' && !schema.const && !schema.type) +const IsResultBooleanSuccess = (schema, name) => (name === 'success' && schema.type === 'boolean') + +function getParamList(schema, module) { + let paramList = [] + if (schema.params.length > 0) { + schema.params.map(p => { + /* + param = {name='', nativeType='', jsonType='', required=boolean} + */ + let param = {} + param['nativeType'] = getSchemaType(p.schema, module, { title: true, name: p.name }) + param['jsonType'] = getJsonType(p.schema, module, {name: p.name}) + param['name'] = p.name + param['required'] = p.required + paramList.push(param) + }) + + } + return paramList +} + +function getMethodSignature(method, module, { destination, isInterface = false }) { + + let signature = '' + if (hasTag(method, 'property') || hasTag(method, 'property:readonly') || hasTag(method, 'property:immutable')) { + let paramList = getParamList(method, module) + let resultType = method.result && getSchemaType(method.result.schema, module, { title: true, name: method.result.name, resultSchema: true}) || '' + + signature = getPropertyGetterSignature(method, module, resultType, paramList) + ';\n' + } + return signature +} + +function getMethodSignatureParams(method, module, { destination, callback = false } = {}) { + let signatureParams = '' + let polymorphicPull = method.tags.find(t => t.name === 'polymorphic-pull') + method.params.map(param => { + if (polymorphicPull && (param.name === 'correlationId')) { + return + } + signatureParams += (signatureParams.length > 0) ? ', ' : '' + let type = getSchemaType(param.schema, module, { name: param.name, title: true, destination }) + if ((callback === true) && (type === 'char*')) { + type = getFireboltStringType() + } + + signatureParams += type + ((!param.required && !type.includes('_t') && (type !== 'char*')) ? '* ' : ' ') + param.name + }) + return signatureParams +} + +const safeName = prop => prop.match(/[.+]/) ? '"' + prop + '"' : prop + +function getSchemaType(schema, module, { name, prefix = '', destination, resultSchema = false, link = false, title = false, code = false, asPath = false, event = false, expandEnums = true, baseUrl = '' } = {}) { + let info = getSchemaTypeInfo(module, schema, name, module['x-schemas'], prefix, { title: title, resultSchema: resultSchema, event: event }) + return info.type +} + +function getSchemaTypeInfo(module = {}, json = {}, name = '', schemas = {}, prefix = '', options = { level: 0, descriptions: true, title: false, resultSchema: false, event: false}) { + + if (json.schema) { + json = json.schema + } + + let fireboltString = options.resultSchema || options.event + + let structure = {} + structure["type"] = '' + structure["json"] = [] + structure["name"] = {} + structure["namespace"] = {} + + if (json['$ref']) { + if (json['$ref'][0] === '#') { + //Ref points to local schema + //Get Path to ref in this module and getSchemaType + let definition = getPath(json['$ref'], module, schemas) + let tName = definition.title || json['$ref'].split('/').pop() + let schema = module + if (json['$ref'].includes('x-schemas')) { + schema = (getRefModule(json['$ref'].split('/')[2])) + } + + const res = getSchemaTypeInfo(schema, definition, tName, schemas, '', options) + structure.type = res.type + structure.json = res.json + structure.name = res.name + structure.namespace = res.namespace + } + } + else if (json.const) { + structure.type = getNativeType(json, fireboltString) + structure.json = json + } + else if (json['x-method']) { + console.log(`WARNING UNHANDLED: x-method in ${name}`) + //throw "x-methods not supported yet" + } + else if (json.type === 'string' && json.enum) { + //Enum + structure.name = name || json.title + let typeName = getTypeName(getModuleName(module), name || json.title, prefix, false, false) + let res = description(capitalize(name || json.title), json.description) + '\n' + generateEnum(json, typeName) + structure.json = json + structure.type = typeName + structure.namespace = getModuleName(module) + } + else if (Array.isArray(json.type)) { + let type = json.type.find(t => t !== 'null') + let sch = JSON.parse(JSON.stringify(json)) + sch.type = type + structure = getSchemaTypeInfo(module, sch, name, schemas, prefix, options) + } + else if (json.type === 'array' && json.items && (validJsonObjectProperties(json) === true)) { + let res = '' + if (Array.isArray(json.items)) { + //TODO + const IsHomogenous = arr => new Set(arr.map( item => item.type ? item.type : typeof item)).size === 1 + if (!IsHomogenous(json.items)) { + throw 'Heterogenous Arrays not supported yet' + } + res = getSchemaTypeInfo(module, json.items[0], json.items[0].name || name, schemas, prefix) + } + else { + // grab the type for the non-array schema + res = getSchemaTypeInfo(module, json.items, json.items.name || name, schemas, prefix) + } + + name = name.endsWith("_ArrayType") ? name.split('_ArrayType')[0] : name + name = capitalize(name) + res.name = capitalize(res.name) + prefix = prefix ? prefix + ((name !== res.name) ? name : '') : name + let n = getTypeName(getModuleName(module), res.name, prefix) + structure.name = (name && (name !== res.name)) ? name + capitalize(res.name) : res.name + structure.type = n + 'Array_t' + structure.json = json + structure.namespace = getModuleName(module) + } + else if (json.allOf) { + let title = json.title ? json.title : name + let union = deepMergeAll(module, title, json, schemas, options) + union['title'] = title + + delete union['$ref'] + structure = getSchemaTypeInfo(module, union, '', schemas, '', options) + } + else if (json.oneOf) { + structure.type = fireboltString ? getFireboltStringType() : 'char*' + structure.json.type = 'string' + } + else if (json.anyOf) { + let mergedSchema = getMergedSchema(module, json, name, schemas) + if (mergedSchema.type) { + let prefixName = ((prefix.length > 0) && (!name.startsWith(prefix))) ? prefix : capitalize(name) + structure = getSchemaTypeInfo(module, mergedSchema, '', schemas, prefixName, options) + } + else { + structure.type = fireboltString ? getFireboltStringType() : 'char*' + structure.json.type = 'string' + } + } + else if (json.type === 'object') { + structure.json = json + if (hasProperties(json)) { + structure.type = getTypeName(getModuleName(module), json.title || name, prefix) + '_t' + structure.name = (json.name ? json.name : (json.title ? json.title : name)) + structure.namespace = (json.namespace ? json.namespace : getModuleName(module)) + } + else { + structure.type = fireboltString ? getFireboltStringType() : 'char*' + } + if (name) { + structure.name = capitalize(name) + } + } + else if (json.type) { + if (!IsResultBooleanSuccess(json, name) && !IsResultConstNullSuccess(json, name)) { + structure.type = getNativeType(json, fireboltString) + structure.json = json + if (name || json.title) { + structure.name = capitalize(name || json.title) + } + structure.namespace = getModuleName(module) + } + } + + return structure +} + +function getSchemaShape(json, module, { name = '', prefix = '', level = 0, title, summary, descriptions = true, destination = '', section = '', enums = true } = {}) { + + let shape = getSchemaShapeInfo(json, module, module['x-schemas'], { name, prefix, merged: false, level, title, summary, descriptions, destination, section, enums }) + return shape +} +function getSchemaShapeInfo(json, module, schemas = {}, { name = '', prefix = '', merged = false, level = 0, title, summary, descriptions = true, destination = '', section = '', enums = true } = {}) { + let shape = '' + + if (destination && section) { + const isHeader = (destination.includes(getJsonDataPrefix().toLowerCase()) !== true) && destination.endsWith(".h") + const isCPP = ((destination.endsWith(".cpp") || destination.includes(getJsonDataPrefix().toLowerCase())) && (section.includes('accessors') !== true)) + + json = JSON.parse(JSON.stringify(json)) + + name = json.title || name + + if (json['$ref']) { + if (json['$ref'][0] === '#') { + //Ref points to local schema + //Get Path to ref in this module and getSchemaType + let schema = getPath(json['$ref'], module, schemas) + const tName = schema.title || json['$ref'].split('/').pop() + if (json['$ref'].includes('x-schemas')) { + schema = (getRefModule(json['$ref'].split('/')[2])) + } + shape = getSchemaShapeInfo(schema, module, schemas, { name: tName, prefix, merged, level, title, summary, descriptions, destination, section, enums }) + } + } + //If the schema is a const, + else if (json.hasOwnProperty('const') && !isCPP) { + if (level > 0) { + + let t = description(capitalize(name), json.description) + typeName = getTypeName(getModuleName(module), name, prefix) + t += (isHeader ? getPropertyAccessors(typeName, capitalize(name), typeof schema.const, { level: level, readonly: true, optional: false }) : getPropertyAccessorsImpl(typeName, getJsonType(schema, module, { level, name }), typeof schema.const, { level: level, readonly: true, optional: false })) + shape += '\n' + t + } + } + else if (json.type === 'object') { + if (!name) { + console.log(`WARNING: unnamed schema in ${module.info.title}.`) + console.dir(json) + shape = '' + } + else if (json.properties && (validJsonObjectProperties(json) === true)) { + let c_shape = '\n' + description(capitalize(name), json.description) + let cpp_shape = '' + let tName = getTypeName(getModuleName(module), name, prefix) + c_shape += '\n' + (isHeader ? getObjectManagement(tName) : getObjectManagementImpl(tName, getJsonType(json, module, { name }))) + let props = [] + let containerName = ((prefix.length > 0) && (!name.startsWith(prefix))) ? (prefix + '_' + capitalize(name)) : capitalize(name) + Object.entries(json.properties).forEach(([pname, prop]) => { + let items + var desc = '\n' + description(capitalize(pname), prop.description) + if (prop.type === 'array') { + if (Array.isArray(prop.items)) { + //TODO + const IsHomogenous = arr => new Set(arr.map( item => item.type ? item.type : typeof item)).size === 1 + if (!IsHomogenous(prop.items)) { + throw 'Heterogenous Arrays not supported yet' + } + items = prop.items[0] + } + else { + // grab the type for the non-array schema + items = prop.items + } + + let info = getSchemaTypeInfo(module, items, items.name || pname, schemas, prefix, {level : level, descriptions: descriptions, title: true}) + if (info.type && info.type.length > 0) { + let objName = tName + '_' + capitalize(prop.title || pname) + info.json.namespace = info.namespace + let moduleProperty = getJsonTypeInfo(module, json, json.title || name, schemas, prefix) + let prefixName = ((prefix.length > 0) && items['$ref']) ? '' : prefix + let subModuleProperty = getJsonTypeInfo(module, info.json, info.name, schemas, prefix) + + let t = description(capitalize(info.name), json.description) + '\n' + t += '\n' + (isHeader ? getArrayAccessors(objName, tName, info.type) : getArrayAccessorsImpl(tName, moduleProperty.type, (tName + '_t'), subModuleProperty.type, capitalize(pname || prop.title), info.type, info.json)) + c_shape += '\n' + t + props.push({name: `${pname}`, type: `WPEFramework::Core::JSON::ArrayType<${subModuleProperty.type}>`}) + } + else { + console.log(`a. WARNING: Type undetermined for ${name}:${pname}`) + } + } else { + if (((merged === false) || ((merged === true) && (pname.includes(name)))) && (prop.type === 'object' || prop.anyOf || prop.allOf)) { + shape += getSchemaShapeInfo(prop, module, schemas, { name : pname, prefix, merged: false, level: 1, title, summary, descriptions, destination, section, enums }) + } + let info = getSchemaTypeInfo(module, prop, pname, module['x-schemas'], prefix, {descriptions: descriptions, level: level + 1, title: true}) + if (info.type && info.type.length > 0) { + let subPropertyName = ((pname.length !== 0) ? capitalize(pname) : info.name) + let moduleProperty = getJsonTypeInfo(module, json, name, schemas, prefix) + let subProperty = getJsonTypeInfo(module, prop, pname, schemas, prefix) + c_shape += '\n' + description(capitalize(pname), info.json.description) + c_shape += '\n' + (isHeader ? getPropertyAccessors(tName, capitalize(pname), info.type, { level: 0, readonly: false, optional: isOptional(pname, json) }) : getPropertyAccessorsImpl(tName, moduleProperty.type, subProperty.type, subPropertyName, info.type, info.json, {readonly:false, optional:isOptional(pname, json)})) + let property = getJsonType(prop, module, { name : pname, prefix }) + props.push({name: `${pname}`, type: `${property}`}) + } + } + }) + + cpp_shape += getJsonContainerDefinition(json, containerName, props) + + if (isCPP) { + shape += '\n' + cpp_shape + } + else { + shape += '\n' + c_shape + } + } + else if (json.propertyNames && json.propertyNames.enum) { + //propertyNames in object not handled yet + } + else if (json.additionalProperties && (typeof json.additionalProperties === 'object') && (validJsonObjectProperties(json) === true) && !isCPP) { + let info = getSchemaTypeInfo(module, json.additionalProperties, name, module['x-schemas'], prefix) + if (!info.type || (info.type.length === 0)) { + info.type = 'char*' + info.json = json.additionalProperties + info.json.type = 'string' + } + + let tName = getTypeName(getModuleName(module), name, prefix) + let t = '\n' + description(capitalize(name), json.description) + let containerType = 'WPEFramework::Core::JSON::VariantContainer' + + let subModuleProperty = getJsonTypeInfo(module, info.json, info.name, module['x-schemas']) + if (isCPP && ((info.json.type === 'object' && info.json.properties) || info.json.type === 'array')) { + // Handle Container generation here + } + + t += '\n' + (isHeader ? getObjectManagement(tName) : getObjectManagementImpl(tName, containerType)) + t += (isHeader ? getMapAccessors(tName, info.type, { descriptions: descriptions, level: level }) : getMapAccessorsImpl(tName, containerType, subModuleProperty.type, info.type, info.json, { readonly: true, optional: false })) + shape += '\n' + t + } + } + else if (json.anyOf) { + if (level > 0) { + let mergedSchema = getMergedSchema(module, json, name, schemas) + if (mergedSchema.type) { + let prefixName = ((prefix.length > 0) && (!name.startsWith(prefix))) ? prefix : capitalize(name) + shape += getSchemaShapeInfo(mergedSchema, module, schemas, { name, prefix: prefixName, merged, level, title, summary, descriptions, destination, section, enums }) + } + } + } + else if (json.oneOf) { + //Just ignore schema shape, since this has to be treated as string + } + else if (json.allOf) { + let title = (json.title ? json.title : name) + let union = deepMergeAll(module, title, json, schemas) + union.title = title + + delete union['$ref'] + + return getSchemaShapeInfo(union, module, schemas, { name, prefix, merged: true, level, title, summary, descriptions, destination, section, enums }) + } + else if (json.type === 'array') { + let j + if (Array.isArray(json.items)) { + //TODO + const IsHomogenous = arr => new Set(arr.map( item => item.type ? item.type : typeof item)).size === 1 + if (!IsHomogenous(json.items)) { + throw 'Heterogenous Arrays not supported yet' + } + j = json.items[0] + } + else { + j = json.items + } + + if (!isCPP) { + name = name.endsWith("_ArrayType") ? name.split('_ArrayType')[0] : name + let subPrefix = prefix ? prefix + name : name + let info = getSchemaTypeInfo(module, j, j.title || name, schemas, subPrefix, {level : level, descriptions: descriptions, title: true}) + + if (info.type && info.type.length > 0) { + let arrayName = capitalize(info.name); + let objName = getTypeName(getModuleName(module), arrayName, subPrefix) + let tName = objName + 'Array' + + info.json.namespace = info.namespace + let moduleProperty = getJsonTypeInfo(module, json, json.title || name, schemas, prefix) + let subModuleProperty = getJsonTypeInfo(module, j, j.title || name, schemas, prefix) + let t = '' + if (level === 0) { + t += '\n' + description(capitalize(info.name), json.description) + t += '\n' + (isHeader ? getObjectManagement(tName) : getObjectManagementImpl(tName, moduleProperty.type)) + } + t += '\n' + (isHeader ? getArrayAccessors(objName, tName, info.type) : getArrayAccessorsImpl(objName, moduleProperty.type, (tName + '_t'), subModuleProperty.type, '', info.type, info.json)) + shape += '\n' + t + } + } + } + else { + shape += '\n' + getSchemaType(module, json, name, schemas, prefix, {level: level, descriptions: descriptions}) + } + } + + return shape +} + +const getJsonNativeType = json => { + let type + let jsonType = json.const ? typeof json.const : json.type + + if (jsonType === 'string') { + type = getSdkNameSpace() + '::JSON::String' + } + else if (jsonType === 'number') { + type = 'WPEFramework::Core::JSON::Float' + } + else if (json.type === 'integer') { + type = 'WPEFramework::Core::JSON::DecSInt32' + } + else if (jsonType === 'boolean') { + type = 'WPEFramework::Core::JSON::Boolean' + } + else if (jsonType === 'null') { + type = 'void' + } + else { + throw 'Unknown JSON Native Type !!!' + } + return type +} + +function getJsonType(schema = {}, module = {}, { name = '', prefix = '', descriptions = false, level = 0 } = {}) { + let info = getJsonTypeInfo(module, schema, name, module['x-schemas'], prefix, { descriptions: descriptions, level: level }) + return info.type +} + +function getJsonTypeInfo(module = {}, json = {}, name = '', schemas, prefix = '', {descriptions = false, level = 0} = {}) { + + if (json.schema) { + json = json.schema + } + + let structure = {} + structure["deps"] = new Set() //To avoid duplication of local ref definitions + structure["type"] = [] + + if (json['$ref']) { + if (json['$ref'][0] === '#') { + //Ref points to local schema + //Get Path to ref in this module and getSchemaType + let definition = getPath(json['$ref'], module, schemas) + let tName = definition.title || json['$ref'].split('/').pop() + + let schema = module + if (json['$ref'].includes('x-schemas')) { + schema = (getRefModule(json['$ref'].split('/')[2])) + } + + const res = getJsonTypeInfo(schema, definition, tName, schemas, '', {descriptions, level}) + structure.deps = res.deps + structure.type = res.type + return structure + } + } + else if (json.const) { + structure.type = getJsonNativeType(json) + return structure + } + else if (json['x-method']) { + return structure + //throw "x-methods not supported yet" + } + else if (json.additionalProperties && (typeof json.additionalProperties === 'object')) { + //This is a map of string to type in schema + //Get the Type + let type = getJsonTypeInfo(module, json.additionalProperties, name, schemas, prefix) + if (type.type && type.type.length > 0) { + structure.type = 'WPEFramework::Core::JSON::VariantContainer'; + return structure + } + else { + console.log(`WARNING: Type undetermined for ${name}`) + } + } + else if (json.type === 'string' && json.enum) { + //Enum + let t = 'WPEFramework::Core::JSON::EnumType<' + (json.namespace ? json.namespace : getModuleName(module)) + '_' + (getEnumName(name, prefix)) + '>' + structure.type.push(t) + return structure + } + else if (Array.isArray(json.type)) { + let type = json.type.find(t => t !== 'null') + let sch = JSON.parse(JSON.stringify(json)) + sch.type = type + return getJsonTypeInfo(module, sch, name, schemas, prefix ) + } + else if (json.type === 'array' && json.items) { + let res + let items + if (Array.isArray(json.items)) { + //TODO + const IsHomogenous = arr => new Set(arr.map( item => item.type ? item.type : typeof item)).size === 1 + if (!IsHomogenous(json.items)) { + throw 'Heterogenous Arrays not supported yet' + } + items = json.items[0] + } + else { + items = json.items + // grab the type for the non-array schema + } + + res = getJsonTypeInfo(module, items, items.name || name, schemas, prefix) + structure.deps = res.deps + structure.type.push(`WPEFramework::Core::JSON::ArrayType<${res.type}>`) + + return structure + } + else if (json.allOf) { + let title = json.title ? json.title : name + let union = deepMergeAll(module, title, json, schemas) + union['title'] = title + + delete union['$ref'] + return getJsonTypeInfo(module, union, '', schemas, '', {descriptions, level}) + } + else if (json.oneOf) { + structure.type = getJsonNativeTypeForOpaqueString() + return structure + } + else if (json.patternProperties) { + structure.type = getJsonNativeTypeForOpaqueString() + return structure + } + else if (json.anyOf) { + let mergedSchema = getMergedSchema(module, json, name, schemas) + if (mergedSchema.type) { + let prefixName = ((prefix.length > 0) && (!name.startsWith(prefix))) ? prefix : capitalize(name) + structure = getJsonTypeInfo(module, mergedSchema, name, schemas, prefixName, {descriptions, level}) + } + else { + structure.type = getJsonNativeTypeForOpaqueString() + } + } + else if (json.type === 'object') { + if (hasProperties(json) !== true) { + structure.type = getJsonNativeTypeForOpaqueString() + } + else { + let schema = getSchemaTypeInfo(module, json, name, module['x-schemas'], prefix) + if (schema.namespace && schema.namespace.length > 0) { + structure.type.push(getJsonDataStructName(schema.namespace, json.title || name, prefix)) + } + } + return structure + } + else if (json.type) { + structure.type = getJsonNativeType(json) + return structure + } + else { + structure.type = 'JsonObject' + } + 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') { + return getParameterInstantiation(getParamList(schema, module)) + } + else if (instantiationType === 'result') { + let result = '' + + if (!IsResultConstNullSuccess(schema, name)) { + let resultJsonType = getJsonType(schema, module, {name: name}) || '' + let resultType = '' + if (!IsResultBooleanSuccess(schema, name)) { + resultType = getSchemaType(schema, module, { title: true, name: name, resultSchema: true}) || '' + } + result = getResultInstantiation(name, resultType, resultJsonType) + } + return result + } + else if (instantiationType === 'callback.params') { + let resultJsonType = getJsonType(schema.result.schema, module, { name: schema.result.name, prefix }) || '' + return getCallbackParametersInstantiation(getParamList(schema, module), resultJsonType) + } + else if (instantiationType === 'callback.result') { + let resultType = getSchemaType(schema.result.schema, module, { title: true, name: schema.result.name, prefix, resultSchema: true}) || '' + let resultJsonType = getJsonType(schema.result.schema, module, { name: schema.result.name }) || '' + return getCallbackResultInstantiation(resultType, resultJsonType) + } + else if (instantiationType === 'callback.response') { + let resultType = getSchemaType(schema.result.schema, module, { title: true, name: schema.result.name, prefix, resultSchema: true}) || '' + let resultJsonType = getJsonType(schema.result.schema, module, { name: schema.result.name, prefix }) || '' + return getCallbackResponseInstantiation(getParamList(schema, module), resultType, resultJsonType) + } + else if (instantiationType === 'pull.param.name') { + let resultJsonType = getJsonType(schema, module, { name: name }) || '' + return resultJsonType.length && resultJsonType[0].split('_')[1] || '' + } + + return '' +} + +export default { + getMethodSignature, + getMethodSignatureParams, + getSchemaShape, + getSchemaType, + getJsonType, + getSchemaInstantiation +} diff --git a/languages/c/language.config.json b/languages/c/language.config.json new file mode 100644 index 00000000..b19eddb8 --- /dev/null +++ b/languages/c/language.config.json @@ -0,0 +1,18 @@ +{ + "name": "C", + "langcode": "c", + "createModuleDirectories": false, + "extractSubSchemas": true, + "templatesPerModule": [ + "/include/module.h", + "/src/module.cpp" + ], + "templatesPerSchema": [ + "/include/common/module.h", + "/src/module_common.cpp", + "/src/jsondata_module.h" + ], + "persistPermission": true, + "createPolymorphicMethods": true, + "excludeDeclarations":true +} diff --git a/languages/c/src/index.mjs b/languages/c/src/index.mjs new file mode 100644 index 00000000..51c547a1 --- /dev/null +++ b/languages/c/src/index.mjs @@ -0,0 +1,19 @@ +/* + * 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 + */ + +export { default as Transport } from './shared/Transport/index.mjs' \ No newline at end of file diff --git a/languages/c/src/shared/CMakeLists.txt b/languages/c/src/shared/CMakeLists.txt new file mode 100644 index 00000000..fe7a0085 --- /dev/null +++ b/languages/c/src/shared/CMakeLists.txt @@ -0,0 +1,56 @@ +# Copyright 2023 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 + +cmake_minimum_required(VERSION 3.3) + +project(Firebolt) + +set(FIREBOLT_TRANSPORT_WAITTIME 1000 CACHE STRING "Maximum time to wait for Transport layer to get response") +set(FIREBOLT_LOGLEVEL "Info" CACHE STRING "Log level to be enabled") +option(FIREBOLT_ENABLE_STATIC_LIB "Create Firebolt library as Static library" OFF) +option(ENABLE_TESTS "Build openrpc native test" OFF) + +if (FIREBOLT_ENABLE_STATIC_LIB) + set(FIREBOLT_LIBRARY_TYPE STATIC) +else () + set(FIREBOLT_LIBRARY_TYPE SHARED) +endif () + +if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${SYSROOT_PATH}/usr" CACHE INTERNAL "" FORCE) + set(CMAKE_PREFIX_PATH ${SYSROOT_PATH}/usr/lib/cmake CACHE INTERNAL "" FORCE) +endif() + +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_SOURCE_DIR}/cmake" + "${SYSROOT_PATH}/usr/lib/cmake" + "${SYSROOT_PATH}/tools/cmake") +include(HelperFunctions) + +set(FIREBOLT_NAMESPACE ${PROJECT_NAME} CACHE STRING "Namespace of the project") + +find_package(WPEFramework CONFIG REQUIRED) + +add_subdirectory(src) + +if (ENABLE_TESTS) + add_subdirectory(test) +endif() + +# make sure others can make use cmake settings of Firebolt OpenRPC +configure_file( "${CMAKE_SOURCE_DIR}/cmake/project.cmake.in" + "${CMAKE_BINARY_DIR}/${FIREBOLT_NAMESPACE}Config.cmake" + @ONLY) diff --git a/languages/c/src/shared/cmake/HelperFunctions.cmake b/languages/c/src/shared/cmake/HelperFunctions.cmake new file mode 100644 index 00000000..eb2ae296 --- /dev/null +++ b/languages/c/src/shared/cmake/HelperFunctions.cmake @@ -0,0 +1,72 @@ +# Copyright 2023 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 + +macro(GetSubDirs subdirs currentdir) + file(GLOB subdirectories RELATIVE ${currentdir} ${currentdir}/*) + set(subdirs "") + foreach(subdir ${subdirectories}) + if (IS_DIRECTORY ${currentdir}/${subdir}) + list(APPEND subdirs ${subdir}) + endif() + endforeach() +endmacro() + +function(InstallHeaders) + set(optionsArgs EXCLUDE_ROOT_DIR) + set(oneValueArgs TARGET NAMESPACE SOURCE DESTINATION) + set(multiValueArgs HEADERS) + + cmake_parse_arguments(Argument "${optionsArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + if (Argument_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unknown keywords given to InstallHeaders(): \"${Argument_UNPARSED_ARGUMENTS}\"") + endif() + if (Argument_HEADERS) + add_custom_command( + TARGET ${Argument_TARGET} + POST_BUILD + COMMENT "=================== Installing Headers ======================" + ) + foreach(directory ${Argument_HEADERS}) + if (Argument_EXCLUDE_ROOT_DIR) + set(destination ${Argument_DESTINATION}) + else() + set(destination ${Argument_DESTINATION}/${directory}) + endif() + + if (Argument_SOURCE) + set(source ${Argument_SOURCE}) + else() + set(source ${CMAKE_CURRENT_LIST_DIR}) + endif() + + GetSubDirs(subdirs ${source}/${directory}) + list(APPEND subdirs ${directory}) + + foreach(subdir ${subdirs}) + if (NOT subdir STREQUAL ".") + set(dest ${destination}/${subdir}) + file(GLOB headers "${source}/${directory}/${subdir}/*.h") + if (headers) + install( + DIRECTORY "${source}/${directory}/${subdir}" + DESTINATION include/${dest} + FILES_MATCHING PATTERN "*.h") + endif() + endif() + endforeach(subdir) + endforeach(directory) + endif() +endfunction(InstallHeaders) diff --git a/languages/c/src/shared/cmake/project.cmake.in b/languages/c/src/shared/cmake/project.cmake.in new file mode 100644 index 00000000..eca32f8c --- /dev/null +++ b/languages/c/src/shared/cmake/project.cmake.in @@ -0,0 +1,35 @@ +# Copyright 2023 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 + +set(FIREBOLT_NAMESPACE "@FIREBOLT_NAMESPACE@" CACHE INTERNAL "" FORCE) +set("${FIREBOLT_NAMESPACE}_FOUND" TRUE CACHE INTERNAL "" FORCE) + +list(APPEND CMAKE_MODULE_PATH + "${CMAKE_SOURCE_DIR}/cmake" + "${SYSROOT_PATH}/usr/lib/cmake" + "${SYSROOT_PATH}/usr/lib/cmake/Firebolt" + "${SYSROOT_PATH}/tools/cmake") + +if (NOT DEFINED CMAKE_PREFIX_PATH) + set(CMAKE_PREFIX_PATH ${SYSROOT_PATH}/usr/lib/cmake CACHE INTERNAL "" FORCE) +endif() + +if (FIREBOLT_ENABLE_STATIC_LIB) + set(FIREBOLT_LIBRARY_TYPE STATIC) +else () + set(FIREBOLT_LIBRARY_TYPE SHARED) +endif () + diff --git a/languages/c/src/shared/include/error.h b/languages/c/src/shared/include/error.h new file mode 100644 index 00000000..07d6268a --- /dev/null +++ b/languages/c/src/shared/include/error.h @@ -0,0 +1,41 @@ +/* + * Copyright 2023 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 + */ + +#ifndef FIREBOLT_ERROR_H +#define FIREBOLT_ERROR_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum FireboltSDKError { + FireboltSDKErrorNone = 0, + FireboltSDKErrorGeneral = 1, + FireboltSDKErrorUnavailable = 2, + FireboltSDKErrorTimedout = 3, + FireboltSDKErrorNotSubscribed = 4, + FireboltSDKErrorUnknown = 5, + FireboltSDKErrorInUse = 6, + FireboltSDKErrorNotSupported = 7 +} FireboltSDKError_t; + +#ifdef __cplusplus +} +#endif + +#endif // FIREBOLT_ERROR_H diff --git a/languages/c/src/shared/include/firebolt.h b/languages/c/src/shared/include/firebolt.h new file mode 100644 index 00000000..565b363c --- /dev/null +++ b/languages/c/src/shared/include/firebolt.h @@ -0,0 +1,68 @@ +/* + * Copyright 2023 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 + */ + +#ifndef FIREBOLT_H +#define FIREBOLT_H + +#include "error.h" +#include "types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define IN +#define OUT + +/** + * @brief Intitialize the Firebolt SDK + * + * @param configLine JSON String with configuration options + * + * CONFIG Format: + * { + * "waitTime": 1000, + * "logLevel": "Info", + * "workerPool":{ + * "queueSize": 8, + * "threadCount": 3 + * }, + * "wsUrl": "ws://127.0.0.1:9998" + * } + * + * + * @return FireboltSDKErrorNone if success, appropriate error otherwise. + * + */ +uint32_t FireboltSDK_Initialize(char* configLine); + + +/** + * @brief Deintitialize the Firebolt SDK + * + * @return FireboltSDKErrorNone if success, appropriate error otherwise. + * + */ +uint32_t FireboltSDK_Deinitialize(void); + +#ifdef __cplusplus +} +#endif + + +#endif // FIREBOLT_H diff --git a/languages/c/src/shared/include/types.h b/languages/c/src/shared/include/types.h new file mode 100644 index 00000000..8333f0eb --- /dev/null +++ b/languages/c/src/shared/include/types.h @@ -0,0 +1,37 @@ +/* + * Copyright 2023 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 + */ + +#ifndef FIREBOLT_TYPES_H +#define FIREBOLT_TYPES_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Firebolt_String_s* Firebolt_String_t; +const char* Firebolt_String(Firebolt_String_t handle); +void Firebolt_String_Release(Firebolt_String_t handle); + +#ifdef __cplusplus +} +#endif + +#endif // FIREBOLT_TYPES_H diff --git a/languages/c/src/shared/src/Accessor/Accessor.cpp b/languages/c/src/shared/src/Accessor/Accessor.cpp new file mode 100644 index 00000000..6d4aceae --- /dev/null +++ b/languages/c/src/shared/src/Accessor/Accessor.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2023 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 + */ + +#include "Accessor.h" + +namespace FireboltSDK { + + Accessor* Accessor::_singleton = nullptr; + + Accessor::Accessor(const string& configLine) + : _workerPool() + , _transport(nullptr) + , _config() + { + _singleton = this; + _config.FromString(configLine); + + Logger::SetLogLevel(WPEFramework::Core::EnumerateType(_config.LogLevel.Value().c_str()).Value()); + + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), "Url = %s", _config.WsUrl.Value().c_str()); + CreateTransport(_config.WsUrl.Value().c_str(), _config.WaitTime.Value()); + CreateEventHandler(); + + _workerPool = WPEFramework::Core::ProxyType::Create(_config.WorkerPool.ThreadCount.Value(), _config.WorkerPool.StackSize.Value(), _config.WorkerPool.QueueSize.Value()); + WPEFramework::Core::WorkerPool::Assign(&(*_workerPool)); + _workerPool->Run(); + } + + Accessor::~Accessor() + { + DestroyTransport(); + DestroyEventHandler(); + WPEFramework::Core::IWorkerPool::Assign(nullptr); + _workerPool->Stop(); + _singleton = nullptr; + } + + int32_t Accessor::CreateEventHandler() + { + Event::Instance().Configure(_transport); + return FireboltSDKErrorNone; + } + + int32_t Accessor::DestroyEventHandler() + { + Event::Dispose(); + return FireboltSDKErrorNone; + } + + Event& Accessor::GetEventManager() + { + return Event::Instance(); + } + + int32_t Accessor::CreateTransport(const string& url, const uint32_t waitTime = DefaultWaitTime) + { + if (_transport != nullptr) { + delete _transport; + } + + _transport = new Transport(static_cast(url), waitTime); + if (WaitForLinkReady(_transport, waitTime) != FireboltSDKErrorNone) { + delete _transport; + _transport = nullptr; + } + + ASSERT(_transport != nullptr); + return ((_transport != nullptr) ? FireboltSDKErrorNone : FireboltSDKErrorUnavailable); + } + + int32_t Accessor::DestroyTransport() + { + if (_transport != nullptr) { + delete _transport; + _transport = nullptr; + } + return FireboltSDKErrorNone; + } + + Transport* Accessor::GetTransport() + { + ASSERT(_transport != nullptr); + return _transport; + } + + int32_t Accessor::WaitForLinkReady(Transport* transport, const uint32_t waitTime = DefaultWaitTime) { + uint32_t waiting = (waitTime == WPEFramework::Core::infinite ? WPEFramework::Core::infinite : waitTime); + static constexpr uint32_t SLEEPSLOT_TIME = 100; + + // Right, a wait till connection is closed is requested.. + while ((waiting > 0) && (transport->IsOpen() == false)) { + + uint32_t sleepSlot = (waiting > SLEEPSLOT_TIME ? SLEEPSLOT_TIME : waiting); + + // Right, lets sleep in slices of 100 ms + SleepMs(sleepSlot); + + waiting -= (waiting == WPEFramework::Core::infinite ? 0 : sleepSlot); + } + return (((waiting == 0) || (transport->IsOpen() == true)) ? FireboltSDKErrorNone : FireboltSDKErrorTimedout); + } +} diff --git a/languages/c/src/shared/src/Accessor/Accessor.h b/languages/c/src/shared/src/Accessor/Accessor.h new file mode 100644 index 00000000..e247df98 --- /dev/null +++ b/languages/c/src/shared/src/Accessor/Accessor.h @@ -0,0 +1,122 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include "Module.h" +#include "WorkerPool.h" +#include "Transport/Transport.h" +#include "Event/Event.h" +#include "Logger/Logger.h" + +namespace FireboltSDK { + class Accessor { + private: + static constexpr uint8_t JSONVersion = 2; + + private: + //Singleton + Accessor(const string& configLine); + + public: + class EXTERNAL Config : public WPEFramework::Core::JSON::Container { + public: + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + + class WorkerPoolConfig : public WPEFramework::Core::JSON::Container { + public: + WorkerPoolConfig& operator=(const WorkerPoolConfig&); + + WorkerPoolConfig() + : WPEFramework::Core::JSON::Container() + , QueueSize(8) + , ThreadCount(3) + , StackSize(WPEFramework::Core::Thread::DefaultStackSize()) + { + Add("queueSize", &QueueSize); + Add("threadCount", &ThreadCount); + Add("stackSize", &StackSize); + } + + virtual ~WorkerPoolConfig() = default; + + public: + WPEFramework::Core::JSON::DecUInt32 QueueSize; + WPEFramework::Core::JSON::DecUInt32 ThreadCount; + WPEFramework::Core::JSON::DecUInt32 StackSize; + }; + + + Config() + : WPEFramework::Core::JSON::Container() + , WaitTime(1000) + , LogLevel(_T("Info")) + , WorkerPool() + , WsUrl(_T("ws://127.0.0.1:9998")) + { + Add(_T("waitTime"), &WaitTime); + Add(_T("logLevel"), &LogLevel); + Add(_T("workerPool"), &WorkerPool); + Add(_T("wsUrl"), &WsUrl); + } + + public: + WPEFramework::Core::JSON::DecUInt32 WaitTime; + WPEFramework::Core::JSON::String LogLevel; + WorkerPoolConfig WorkerPool; + WPEFramework::Core::JSON::String WsUrl; + }; + + Accessor(const Accessor&) = delete; + Accessor& operator= (const Accessor&) = delete; + Accessor() = delete; + ~Accessor(); + + static Accessor& Instance(const string& configLine = "") + { + static Accessor *instance = new Accessor(configLine); + ASSERT(instance != nullptr); + return *instance; + } + + static void Dispose() + { + ASSERT(_singleton != nullptr); + + if (_singleton != nullptr) { + delete _singleton; + } + } + Event& GetEventManager(); + Transport* GetTransport(); + + private: + int32_t CreateEventHandler(); + int32_t DestroyEventHandler(); + int32_t CreateTransport(const string& url, const uint32_t waitTime); + int32_t DestroyTransport(); + int32_t WaitForLinkReady(Transport* transport, const uint32_t waitTime); + + private: + WPEFramework::Core::ProxyType _workerPool; + Transport* _transport; + static Accessor* _singleton; + Config _config; + }; +} diff --git a/languages/c/src/shared/src/Accessor/WorkerPool.h b/languages/c/src/shared/src/Accessor/WorkerPool.h new file mode 100644 index 00000000..69005a5e --- /dev/null +++ b/languages/c/src/shared/src/Accessor/WorkerPool.h @@ -0,0 +1,102 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include "Module.h" + +namespace FireboltSDK { + + class WorkerPoolImplementation : public WPEFramework::Core::WorkerPool { + public: + WorkerPoolImplementation() = delete; + WorkerPoolImplementation(const WorkerPoolImplementation&) = delete; + WorkerPoolImplementation& operator=(const WorkerPoolImplementation&) = delete; + + WorkerPoolImplementation(const uint8_t threads, const uint32_t stackSize, const uint32_t queueSize) + : WorkerPool(threads, stackSize, queueSize, &_dispatcher) + { + } + + ~WorkerPoolImplementation() + { + // Diable the queue so the minions can stop, even if they are processing and waiting for work.. + Stop(); + } + + public: + void Stop() + { + WPEFramework::Core::WorkerPool::Stop(); + } + + void Run() + { + WPEFramework::Core::WorkerPool::Run(); + } + + private: + class Dispatcher : public WPEFramework::Core::ThreadPool::IDispatcher { + public: + Dispatcher(const Dispatcher&) = delete; + Dispatcher& operator=(const Dispatcher&) = delete; + + Dispatcher() = default; + ~Dispatcher() override = default; + + private: + void Initialize() override { } + void Deinitialize() override { } + void Dispatch(WPEFramework::Core::IDispatch* job) override + { job->Dispatch(); } + }; + + Dispatcher _dispatcher; + }; + + class Worker : public WPEFramework::Core::IDispatch { + public: + typedef std::function Dispatcher; + + protected: + Worker(const Dispatcher& dispatcher, const void* userData) + : _dispatcher(dispatcher) + , _userData(userData) + { + } + + public: + Worker() = delete; + Worker(const Worker&) = delete; + Worker& operator=(const Worker&) = delete; + + ~Worker() = default; + + public: + static WPEFramework::Core::ProxyType Create(const Dispatcher& dispatcher, const void* userData); + + void Dispatch() override + { + _dispatcher(_userData); + } + + private: + Dispatcher _dispatcher; + const void* _userData; + }; +} diff --git a/languages/c/src/shared/src/CMakeLists.txt b/languages/c/src/shared/src/CMakeLists.txt new file mode 100644 index 00000000..ecaf4efb --- /dev/null +++ b/languages/c/src/shared/src/CMakeLists.txt @@ -0,0 +1,73 @@ +# Copyright 2023 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 + +cmake_minimum_required(VERSION 3.3) + +project(FireboltSDK) +project_version(1.0.0) +set(TARGET ${PROJECT_NAME}) +message("Setup ${TARGET} v${PROJECT_VERSION}") + +file(GLOB SOURCES *.cpp) +add_library(${TARGET} ${FIREBOLT_LIBRARY_TYPE} + ${SOURCES} + Logger/Logger.cpp + Transport/Transport.cpp + Accessor/Accessor.cpp + Event/Event.cpp +) + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +find_package(${NAMESPACE}WebSocket CONFIG REQUIRED) + +target_link_libraries(${TARGET} + PUBLIC + ${NAMESPACE}WebSocket::${NAMESPACE}WebSocket +) + +target_include_directories(${TARGET} + PRIVATE + $ + $ +) + +set_target_properties(${TARGET} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + FRAMEWORK FALSE + LINK_WHAT_YOU_USE TRUE + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR} +) + +install( + TARGETS ${TARGET} EXPORT ${TARGET}Targets + ARCHIVE DESTINATION lib COMPONENT libs # static lib + LIBRARY DESTINATION lib COMPONENT libs # shared lib +) + +install( + DIRECTORY ${CMAKE_SOURCE_DIR}/include/ + DESTINATION include/${FIREBOLT_NAMESPACE}SDK + FILES_MATCHING PATTERN "*.h") + +install( + FILES ${CMAKE_BINARY_DIR}/FireboltConfig.cmake + DESTINATION lib/cmake/${FIREBOLT_NAMESPACE}) + +InstallHeaders(TARGET ${TARGET} HEADERS . NAMESPACE ${FIREBOLT_NAMESPACE} DESTINATION ${FIREBOLT_NAMESPACE}SDK) +InstallCMakeConfig(TARGETS ${TARGET}) +InstallPackageConfig(TARGETS ${TARGET} DESCRIPTION "Firebolt SDK Library") diff --git a/languages/c/src/shared/src/Event/Event.cpp b/languages/c/src/shared/src/Event/Event.cpp new file mode 100644 index 00000000..c1fdff6f --- /dev/null +++ b/languages/c/src/shared/src/Event/Event.cpp @@ -0,0 +1,147 @@ +/* + * Copyright 2023 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 + */ + +#include "Transport/Transport.h" +#include "Event.h" + +namespace FireboltSDK { + Event* Event::_singleton = nullptr; + Event::Event() + : _eventMap() + , _adminLock() + , _transport(nullptr) + { + ASSERT(_singleton == nullptr); + _singleton = this; + } + + Event::~Event() /* override */ + { + _transport->SetEventHandler(nullptr); + _transport = nullptr; + + _singleton = nullptr; + } + + /* static */ Event& Event::Instance() + { + static Event *instance = new Event(); + ASSERT(instance != nullptr); + return *instance; + } + + /* static */ void Event::Dispose() + { + ASSERT(_singleton != nullptr); + + if (_singleton != nullptr) { + delete _singleton; + } + } + + void Event::Configure(Transport* transport) + { + _transport = transport; + _transport->SetEventHandler(this); + } + + int32_t Event::Unsubscribe(const string& eventName, void* usercb) + { + int32_t status = Revoke(eventName, usercb); + + if (status == FireboltSDKErrorNone) { + if (_transport != nullptr) { + + const string parameters("{\"listen\":false}"); + status = _transport->Unsubscribe(eventName, parameters); + } + } + return ((status == FireboltSDKErrorInUse) ? FireboltSDKErrorNone: status); + } + + int32_t Event::ValidateResponse(const WPEFramework::Core::ProxyType& jsonResponse, bool& enabled) /* override */ + { + int32_t result = FireboltSDKErrorGeneral; + Response response; + _transport->FromMessage((WPEFramework::Core::JSON::IElement*)&response, *jsonResponse); + if (response.Listening.IsSet() == true) { + result = FireboltSDKErrorNone; + enabled = response.Listening.Value(); + } + return result; + } + + int32_t Event::Dispatch(const string& eventName, const WPEFramework::Core::ProxyType& jsonResponse) /* override */ + { + string response = jsonResponse->Result.Value(); + _adminLock.Lock(); + EventMap::iterator eventIndex = _eventMap.find(eventName); + if (eventIndex != _eventMap.end()) { + CallbackMap::iterator callbackIndex = eventIndex->second.begin(); + while(callbackIndex != eventIndex->second.end()) { + State state; + if (callbackIndex->second.state != State::REVOKED) { + callbackIndex->second.state = State::EXECUTING; + } + state = callbackIndex->second.state; + _adminLock.Unlock(); + if (state == State::EXECUTING) { + callbackIndex->second.lambda(callbackIndex->first, callbackIndex->second.userdata, (jsonResponse->Result.Value())); + } + _adminLock.Lock(); + if (callbackIndex->second.state == State::REVOKED) { + callbackIndex = eventIndex->second.erase(callbackIndex); + if (eventIndex->second.size() == 0) { + _eventMap.erase(eventIndex); + } + } else { + callbackIndex->second.state = State::IDLE; + callbackIndex++; + } + } + } + _adminLock.Unlock(); + + return FireboltSDKErrorNone;; + } + + int32_t Event::Revoke(const string& eventName, void* usercb) + { + int32_t status = FireboltSDKErrorNone; + _adminLock.Lock(); + EventMap::iterator eventIndex = _eventMap.find(eventName); + if (eventIndex != _eventMap.end()) { + CallbackMap::iterator callbackIndex = eventIndex->second.find(usercb); + if (callbackIndex->second.state != State::EXECUTING) { + if (callbackIndex != eventIndex->second.end()) { + eventIndex->second.erase(callbackIndex); + } + } else { + callbackIndex->second.state = State::REVOKED; + } + if (eventIndex->second.size() == 0) { + _eventMap.erase(eventIndex); + } else { + status = FireboltSDKErrorInUse; + } + } + _adminLock.Unlock(); + + return status; + } +} diff --git a/languages/c/src/shared/src/Event/Event.h b/languages/c/src/shared/src/Event/Event.h new file mode 100644 index 00000000..dd65cb53 --- /dev/null +++ b/languages/c/src/shared/src/Event/Event.h @@ -0,0 +1,165 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include "Module.h" + +namespace FireboltSDK { + + static constexpr uint32_t DefaultWaitTime = 1000; + + class Event : public IEventHandler { + public: + typedef std::function DispatchFunction; + private: + enum State : uint8_t { + IDLE, + EXECUTING, + REVOKED + }; + + struct CallbackData { + const DispatchFunction lambda; + const void* userdata; + State state; + }; + using CallbackMap = std::map; + using EventMap = std::map; + + class Response : public WPEFramework::Core::JSON::Container { + public: + Response& operator=(const Response&) = delete; + Response() + : WPEFramework::Core::JSON::Container() + , Listening(false) + { + Add(_T("listening"), &Listening); + } + Response(const Response& copy) + : WPEFramework::Core::JSON::Container() + , Listening(copy.Listening) + { + Add(_T("listening"), &Listening); + } + ~Response() override = default; + + public: + WPEFramework::Core::JSON::Boolean Listening; + }; + + private: + Event(); + public: + ~Event() override; + static Event& Instance(); + static void Dispose(); + void Configure(Transport* transport); + + public: + template + int32_t Subscribe(const string& eventName, const CALLBACK& callback, void* usercb, const void* userdata) + { + JsonObject jsonParameters; + return Subscribe(eventName, jsonParameters, callback, usercb, userdata); + } + + template + int32_t Subscribe(const string& eventName, JsonObject& jsonParameters, const CALLBACK& callback, void* usercb, const void* userdata) + { + int32_t status = FireboltSDKErrorUnavailable; + if (_transport != nullptr) { + + status = Assign(eventName, callback, usercb, userdata); + if (status == FireboltSDKErrorNone) { + Response response; + + WPEFramework::Core::JSON::Variant Listen = true; + jsonParameters.Set(_T("listen"), Listen); + string parameters; + jsonParameters.ToString(parameters); + + status = _transport->Subscribe(eventName, parameters, response); + + if (status != FireboltSDKErrorNone) { + Revoke(eventName, usercb); + } else if ((response.Listening.IsSet() == true) && + (response.Listening.Value() == true)) { + status = FireboltSDKErrorNone; + } else { + status = FireboltSDKErrorNotSubscribed; + } + } + } + + return status; + } + + int32_t Unsubscribe(const string& eventName, void* usercb); + + private: + template + int32_t Assign(const string& eventName, const CALLBACK& callback, void* usercb, const void* userdata) + { + int32_t status = FireboltSDKErrorNone; + std::function actualCallback = callback; + DispatchFunction implementation = [actualCallback](void* usercb, const void* userdata, const string& parameters) -> int32_t { + + WPEFramework::Core::ProxyType* inbound = new WPEFramework::Core::ProxyType(); + *inbound = WPEFramework::Core::ProxyType::Create(); + (*inbound)->FromString(parameters); + actualCallback(usercb, userdata, static_cast(inbound)); + return (FireboltSDKErrorNone); + }; + CallbackData callbackData = {implementation, userdata, State::IDLE}; + + _adminLock.Lock(); + EventMap::iterator eventIndex = _eventMap.find(eventName); + if (eventIndex != _eventMap.end()) { + CallbackMap::iterator callbackIndex = eventIndex->second.find(usercb); + if (callbackIndex == eventIndex->second.end()) { + eventIndex->second.emplace(std::piecewise_construct, std::forward_as_tuple(usercb), std::forward_as_tuple(callbackData)); + } else { + // Already registered, no need to register again; + status = FireboltSDKErrorInUse; + } + } else { + + CallbackMap callbackMap; + callbackMap.emplace(std::piecewise_construct, std::forward_as_tuple(usercb), std::forward_as_tuple(callbackData)); + _eventMap.emplace(std::piecewise_construct, std::forward_as_tuple(eventName), std::forward_as_tuple(callbackMap)); + + } + + _adminLock.Unlock(); + return status; + } + int32_t Revoke(const string& eventName, void* usercb); + + private: + int32_t ValidateResponse(const WPEFramework::Core::ProxyType& jsonResponse, bool& enabled) override; + int32_t Dispatch(const string& eventName, const WPEFramework::Core::ProxyType& jsonResponse) override; + + private: + EventMap _eventMap; + WPEFramework::Core::CriticalSection _adminLock; + Transport* _transport; + + static Event* _singleton; + }; +} diff --git a/languages/c/src/shared/src/FireboltSDK.conf.in b/languages/c/src/shared/src/FireboltSDK.conf.in new file mode 100644 index 00000000..6964a7bc --- /dev/null +++ b/languages/c/src/shared/src/FireboltSDK.conf.in @@ -0,0 +1,3 @@ +url = "@FIREBOLT_SERVER_URL@" +waittime = "@FIREBOLT_TRANSPORT_WAITTIME@" +loglevel = "@FIREBOLT_LOGLEVEL@" diff --git a/languages/c/src/shared/src/FireboltSDK.h b/languages/c/src/shared/src/FireboltSDK.h new file mode 100644 index 00000000..d78687dc --- /dev/null +++ b/languages/c/src/shared/src/FireboltSDK.h @@ -0,0 +1,26 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include "Transport/Transport.h" +#include "Properties/Properties.h" +#include "Accessor/Accessor.h" +#include "Logger/Logger.h" +#include "TypesPriv.h" +#include "types.h" diff --git a/languages/c/src/shared/src/Logger/Logger.cpp b/languages/c/src/shared/src/Logger/Logger.cpp new file mode 100644 index 00000000..44ebaba8 --- /dev/null +++ b/languages/c/src/shared/src/Logger/Logger.cpp @@ -0,0 +1,86 @@ +/* + * Copyright 2023 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 + */ + +#include "Module.h" +#include "error.h" +#include "Logger.h" + +namespace WPEFramework { + +ENUM_CONVERSION_BEGIN(FireboltSDK::Logger::LogLevel) + + { FireboltSDK::Logger::LogLevel::Error, _TXT("Error") }, + { FireboltSDK::Logger::LogLevel::Warning, _TXT("Warning") }, + { FireboltSDK::Logger::LogLevel::Info, _TXT("Info") }, + { FireboltSDK::Logger::LogLevel::Debug, _TXT("Debug") }, + +ENUM_CONVERSION_END(FireboltSDK::Logger::LogLevel) + +ENUM_CONVERSION_BEGIN(FireboltSDK::Logger::Category) + + { FireboltSDK::Logger::Category::OpenRPC, _TXT("FireboltSDK::OpenRPC") }, + { FireboltSDK::Logger::Category::Core, _TXT("FireboltSDK::Core") }, + { FireboltSDK::Logger::Category::Management, _TXT("FireboltSDK::Management") }, + { FireboltSDK::Logger::Category::Discovery, _TXT("FireboltSDK::Discovery") }, + +ENUM_CONVERSION_END(FireboltSDK::Logger::Category) + +} + +namespace FireboltSDK { + /* static */ Logger::LogLevel Logger::_logLevel = Logger::LogLevel::Error; + + int32_t Logger::SetLogLevel(Logger::LogLevel logLevel) + { + ASSERT(logLevel < Logger::LogLevel::MaxLevel); + int32_t status = FireboltSDKErrorNotSupported; + if (logLevel < Logger::LogLevel::MaxLevel) { + _logLevel = logLevel; + status = FireboltSDKErrorNone; + } + return status; + } + + void Logger::Log(LogLevel logLevel, Category category, const std::string& module, const std::string file, const std::string function, const uint16_t line, const std::string& format, ...) + { + if (logLevel <= _logLevel) { + va_list arg; + char msg[Logger::MaxBufSize]; + va_start(arg, format); + int length = vsnprintf(msg, Logger::MaxBufSize, format.c_str(), arg); + va_end(arg); + + uint32_t position = (length >= Logger::MaxBufSize) ? (Logger::MaxBufSize - 1) : length; + msg[position] = '\0'; + + char formattedMsg[Logger::MaxBufSize]; + const string time = WPEFramework::Core::Time::Now().ToTimeOnly(true); + const string categoryName = WPEFramework::Core::EnumerateType(category).Data(); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-truncation" + if (categoryName.empty() != true) { + snprintf(formattedMsg, sizeof(formattedMsg), "--->\033[1;32m[%s]:[%s]:[%s][%s:%d](%s) : %s\n", time.c_str(), categoryName.c_str(), module.c_str(), WPEFramework::Core::File::FileName(file).c_str(), line, function.c_str(), TRACE_PROCESS_ID, TRACE_THREAD_ID, msg); + } else { + snprintf(formattedMsg, sizeof(formattedMsg), "--->\033[1;32m[%s]:[%s][%s:%d](%s) : %s\n", time.c_str(), module.c_str(), WPEFramework::Core::File::FileName(file).c_str(), line, function.c_str(), TRACE_PROCESS_ID, TRACE_THREAD_ID, msg); + } +#pragma GCC diagnostic pop + LOG_MESSAGE(formattedMsg); + } + } +} + diff --git a/languages/c/src/shared/src/Logger/Logger.h b/languages/c/src/shared/src/Logger/Logger.h new file mode 100644 index 00000000..b8e42031 --- /dev/null +++ b/languages/c/src/shared/src/Logger/Logger.h @@ -0,0 +1,85 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include "types.h" + +namespace FireboltSDK { + + class Logger { + private: + static constexpr uint16_t MaxBufSize = 512; + + public: + enum class LogLevel : uint8_t { + Error, + Warning, + Info, + Debug, + MaxLevel + }; + + enum class Category : uint8_t { + OpenRPC, + Core, + Management, + Discovery + }; + + public: + Logger() = default; + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + ~Logger() = default; + + public: + static int32_t SetLogLevel(LogLevel logLevel); + static void Log(LogLevel logLevel, Category category, const std::string& module, const std::string file, const std::string function, const uint16_t line, const std::string& format, ...); + + public: + template + static const string Module() + { + return WPEFramework::Core::ClassNameOnly(typeid(CLASS).name()).Text(); + } + + private: + static LogLevel _logLevel; + }; +} + +#define FIREBOLT_LOG(level, category, module, ...) \ + FireboltSDK::Logger::Log(level, category, module, __FILE__, __func__, __LINE__, __VA_ARGS__) + +#define FIREBOLT_LOG_ERROR(category, module, ...) \ + FIREBOLT_LOG(FireboltSDK::Logger::LogLevel::Error, category, module, __VA_ARGS__) +#define FIREBOLT_LOG_WARNING(category, module, ...) \ + FIREBOLT_LOG(FireboltSDK::Logger::LogLevel::Warning, category, module, __VA_ARGS__) +#define FIREBOLT_LOG_INFO(category, module, ...) \ + FIREBOLT_LOG(FireboltSDK::Logger::LogLevel::Info, category, module, __VA_ARGS__) +#define FIREBOLT_LOG_DEBUG(category, module, ...) \ + FIREBOLT_LOG(FireboltSDK::Logger::LogLevel::Debug, category, module, __VA_ARGS__) + +#ifdef ENABLE_SYSLOG +#define LOG_MESSAGE(message) \ + syslog(sLOG_NOTIC, "%s", message); +#else +#define LOG_MESSAGE(message) \ + fprintf(stderr, "%s", message); fflush(stdout); +#endif diff --git a/languages/c/src/shared/src/Module.cpp b/languages/c/src/shared/src/Module.cpp new file mode 100644 index 00000000..d63badc4 --- /dev/null +++ b/languages/c/src/shared/src/Module.cpp @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 + */ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/languages/c/src/shared/src/Module.h b/languages/c/src/shared/src/Module.h new file mode 100644 index 00000000..00ea64bb --- /dev/null +++ b/languages/c/src/shared/src/Module.h @@ -0,0 +1,29 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#ifndef MODULE_NAME +#define MODULE_NAME OpenRPCNativeSDK +#endif + +#include +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/languages/c/src/shared/src/Properties/Properties.h b/languages/c/src/shared/src/Properties/Properties.h new file mode 100644 index 00000000..eaf6c1a4 --- /dev/null +++ b/languages/c/src/shared/src/Properties/Properties.h @@ -0,0 +1,148 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include "Accessor/Accessor.h" +#include "Event/Event.h" + +namespace FireboltSDK { + + class Properties { + public: + Properties(const Properties&) = delete; + Properties& operator= (const Properties&) = delete; + + Properties() = default; + ~Properties() = default; + + public: + template + static int32_t Get(const string& propertyName, WPEFramework::Core::ProxyType& response) + { + int32_t status = FireboltSDKErrorUnavailable; + Transport* transport = Accessor::Instance().GetTransport(); + if (transport != nullptr) { + JsonObject parameters; + RESPONSETYPE responseType; + status = transport->Invoke(propertyName, parameters, responseType); + if (status == FireboltSDKErrorNone) { + ASSERT(response.IsValid() == false); + if (response.IsValid() == true) { + response.Release(); + } + response = WPEFramework::Core::ProxyType::Create(); + (*response) = responseType; + } + } else { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Error in getting Transport err = %d", status); + } + + return status; + } + + template + static int32_t Get(const string& propertyName, const PARAMETERS& parameters, WPEFramework::Core::ProxyType& response) + { + int32_t status = FireboltSDKErrorUnavailable; + Transport* transport = Accessor::Instance().GetTransport(); + if (transport != nullptr) { + RESPONSETYPE responseType; + status = transport->Invoke(propertyName, parameters, responseType); + if (status == FireboltSDKErrorNone) { + ASSERT(response.IsValid() == false); + if (response.IsValid() == true) { + response.Release(); + } + response = WPEFramework::Core::ProxyType::Create(); + (*response) = responseType; + } + } else { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Error in getting Transport err = %d", status); + } + + return status; + } + + + template + static int32_t Get(const string& propertyName, RESPONSETYPE& response) + { + int32_t status = FireboltSDKErrorUnavailable; + Transport* transport = Accessor::Instance().GetTransport(); + if (transport != nullptr) { + JsonObject parameters; + status = transport->Invoke(propertyName, parameters, response); + } else { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Error in getting Transport err = %d", status); + } + + return status; + } + + template + static int32_t Get(const string& propertyName, const PARAMETERS& parameters, RESPONSETYPE& response) + { + int32_t status = FireboltSDKErrorUnavailable; + Transport* transport = Accessor::Instance().GetTransport(); + if (transport != nullptr) { + status = transport->Invoke(propertyName, parameters, response); + } else { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Error in getting Transport err = %d", status); + } + + return status; + } + + template + static int32_t Set(const string& propertyName, const PARAMETERS& parameters) + { + int32_t status = FireboltSDKErrorUnavailable; + Transport* transport = Accessor::Instance().GetTransport(); + if (transport != nullptr) { + JsonObject responseType; + status = transport->Invoke(propertyName, parameters, responseType); + } else { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Error in getting Transport err = %d", status); + } + + return status; + } + + template + static int32_t Subscribe(const string& propertyName, JsonObject& paramsters, const CALLBACK& callback, void* usercb, const void* userdata) + { + return Event::Instance().Subscribe(EventName(propertyName), paramsters, callback, usercb, userdata); + } + + static int32_t Unsubscribe(const string& propertyName, void* usercb) + { + return Event::Instance().Unsubscribe(EventName(propertyName), usercb); + } + private: + static inline string EventName(const string& propertyName) { + size_t pos = propertyName.find_first_of('.'); + string eventName = propertyName; + if (pos != std::string::npos) { + eventName[pos + 1] = std::toupper(eventName[pos + 1]); + eventName = string(eventName.substr(0, pos + 1) + "on" + eventName.substr(pos + 1) + "Changed"); + } + return eventName; + } + }; +} diff --git a/languages/c/src/shared/src/Transport/Transport.cpp b/languages/c/src/shared/src/Transport/Transport.cpp new file mode 100644 index 00000000..280944c6 --- /dev/null +++ b/languages/c/src/shared/src/Transport/Transport.cpp @@ -0,0 +1,24 @@ +/* + * Copyright 2023 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 + */ + +#include "Transport.h" + +namespace FireboltSDK { + +} + diff --git a/languages/c/src/shared/src/Transport/Transport.h b/languages/c/src/shared/src/Transport/Transport.h new file mode 100644 index 00000000..6b80cd25 --- /dev/null +++ b/languages/c/src/shared/src/Transport/Transport.h @@ -0,0 +1,897 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include "Module.h" +#include "error.h" + +namespace FireboltSDK { + + using namespace WPEFramework::Core::TypeTraits; + + template + class CommunicationChannel { + public: + typedef std::function Callback; + class Entry { + private: + Entry(const Entry&) = delete; + Entry& operator=(const Entry& rhs) = delete; + struct Synchronous { + Synchronous() + : _signal(false, true) + , _response() + { + } + WPEFramework::Core::Event _signal; + std::list> _response; + }; + struct ASynchronous { + ASynchronous(const uint32_t waitTime, const Callback& completed) + : _waitTime(WPEFramework::Core::Time::Now().Add(waitTime).Ticks()) + , _completed(completed) + { + } + uint64_t _waitTime; + Callback _completed; + }; + + public: + Entry() + : _synchronous(true) + , _info() + { + } + Entry(const uint32_t waitTime, const Callback& completed) + : _synchronous(false) + , _info(waitTime, completed) + { + } + ~Entry() + { + if (_synchronous == true) { + _info.sync.~Synchronous(); + } + else { + _info.async.~ASynchronous(); + } + } + + public: + const WPEFramework::Core::ProxyType& Response() const + { + return (*(_info.sync._response.begin())); + } + bool Signal(const WPEFramework::Core::ProxyType& response) + { + if (_synchronous == true) { + _info.sync._response.push_back(response); + _info.sync._signal.SetEvent(); + } + else { + _info.async._completed(*response); + } + + return (_synchronous == false); + } + const uint64_t& Expiry() const + { + return (_info.async._waitTime); + } + void Abort(const uint32_t id) + { + if (_synchronous == true) { + _info.sync._signal.SetEvent(); + } + else { + MESSAGETYPE message; + ToMessage(id, message, WPEFramework::Core::ERROR_ASYNC_ABORTED); + _info.async._completed(message); + } + } + bool Expired(const uint32_t id, const uint64_t& currentTime, uint64_t& nextTime) + { + bool expired = false; + + if (_synchronous == false) { + if (_info.async._waitTime > currentTime) { + if (_info.async._waitTime < nextTime) { + nextTime = _info.async._waitTime; + } + } + else { + MESSAGETYPE message; + ToMessage(id, message, WPEFramework::Core::ERROR_TIMEDOUT); + _info.async._completed(message); + expired = true; + } + } + return (expired); + } + bool WaitForResponse(const uint32_t waitTime) + { + return (_info.sync._signal.Lock(waitTime) == WPEFramework::Core::ERROR_NONE); + } + + private: + void ToMessage(const uint32_t id, WPEFramework::Core::JSONRPC::Message& message, uint32_t error) + { + message.Id = id; + message.Error.Code = error; + switch (error) { + case WPEFramework::Core::ERROR_ASYNC_ABORTED: { + message.Error.Text = _T("Pending a-sync call has been aborted"); + break; + } + case WPEFramework::Core::ERROR_TIMEDOUT: { + message.Error.Text = _T("Pending a-sync call has timed out"); + break; + } + } + } + + bool _synchronous; + union Info { + public: + Info() + : sync() + { + } + Info(const uint32_t waitTime, const Callback& completed) + : async(waitTime, completed) + { + } + ~Info() + { + } + Synchronous sync; + ASynchronous async; + } _info; + }; + + + + private: + class FactoryImpl { + private: + FactoryImpl(const FactoryImpl&) = delete; + FactoryImpl& operator=(const FactoryImpl&) = delete; + + class WatchDog { + private: + WatchDog() = delete; + WatchDog& operator=(const WatchDog&) = delete; + + public: + WatchDog(CLIENT* client) + : _client(client) + { + } + WatchDog(const WatchDog& copy) + : _client(copy._client) + { + } + ~WatchDog() + { + } + + bool operator==(const WatchDog& rhs) const + { + return (rhs._client == _client); + } + bool operator!=(const WatchDog& rhs) const + { + return (!operator==(rhs)); + } + + public: + uint64_t Timed(const uint64_t scheduledTime) { + return (_client->Timed()); + } + + private: + CLIENT* _client; + }; + + friend WPEFramework::Core::SingletonType; + + FactoryImpl() + : _messageFactory(2) + , _watchDog(WPEFramework::Core::Thread::DefaultStackSize(), _T("TransportCleaner")) + { + } + + public: + static FactoryImpl& Instance() + { + return (WPEFramework::Core::SingletonType::Instance()); + } + + ~FactoryImpl() + { + } + + public: + WPEFramework::Core::ProxyType Element(const string&) + { + return (_messageFactory.Element()); + } + void Trigger(const uint64_t& time, CLIENT* client) + { + _watchDog.Trigger(time, client); + } + void Revoke(CLIENT* client) + { + _watchDog.Revoke(client); + } + private: + WPEFramework::Core::ProxyPoolType _messageFactory; + WPEFramework::Core::TimerType _watchDog; + }; + + class ChannelImpl : public WPEFramework::Core::StreamJSONType, FactoryImpl&, INTERFACE> { + private: + ChannelImpl(const ChannelImpl&) = delete; + ChannelImpl& operator=(const ChannelImpl&) = delete; + + typedef WPEFramework::Core::StreamJSONType, FactoryImpl&, INTERFACE> BaseClass; + + public: + ChannelImpl(CommunicationChannel* parent, const WPEFramework::Core::NodeId& remoteNode, const string& path, const string& query, const bool mask) + : BaseClass(5, FactoryImpl::Instance(), path, _T("JSON"), query, "", false, mask, false, remoteNode.AnyInterface(), remoteNode, 512, 512) + , _parent(*parent) + { + } + ~ChannelImpl() override = default; + + public: + void Received(WPEFramework::Core::ProxyType& response) override + { + WPEFramework::Core::ProxyType inbound(response); + + ASSERT(inbound.IsValid() == true); + if (inbound.IsValid() == true) { + _parent.Inbound(inbound); + } + } + void Send(WPEFramework::Core::ProxyType& msg) override + { +#ifdef __DEBUG__ + string message; + ToMessage(msg, message); + TRACE_L1("Message: %s send", message.c_str()); +#endif + } + void StateChange() override + { + _parent.StateChange(); + } + bool IsIdle() const override + { + return (true); + } + + private: + void ToMessage(const WPEFramework::Core::ProxyType& jsonObject, string& message) const + { + WPEFramework::Core::ProxyType inbound(jsonObject); + + ASSERT(inbound.IsValid() == true); + if (inbound.IsValid() == true) { + inbound->ToString(message); + } + } + void ToMessage(const WPEFramework::Core::ProxyType& jsonObject, string& message) const + { + WPEFramework::Core::ProxyType inbound(jsonObject); + + ASSERT(inbound.IsValid() == true); + if (inbound.IsValid() == true) { + std::vector values; + inbound->ToBuffer(values); + if (values.empty() != true) { + WPEFramework::Core::ToString(values.data(), static_cast(values.size()), false, message); + } + } + } + + private: + CommunicationChannel& _parent; + }; + + protected: + CommunicationChannel(const WPEFramework::Core::NodeId& remoteNode, const string& path, const string& query, const bool mask) + : _channel(this, remoteNode, path, query, mask) + , _sequence(0) + { + } + + public: + ~CommunicationChannel() = default; + static WPEFramework::Core::ProxyType Instance(const WPEFramework::Core::NodeId& remoteNode, const string& path, const string& query, const bool mask = true) + { + static WPEFramework::Core::ProxyMapType channelMap; + + string searchLine = remoteNode.HostAddress() + '@' + path; + + return (channelMap.template Instance(searchLine, remoteNode, path, query, mask)); + } + + public: + static void Trigger(const uint64_t& time, CLIENT* client) + { + FactoryImpl::Instance().Trigger(time, client); + } + static WPEFramework::Core::ProxyType Message() + { + return (FactoryImpl::Instance().Element(string())); + } + uint32_t Sequence() const + { + return (++_sequence); + } + void Register(CLIENT& client) + { + _adminLock.Lock(); + ASSERT(std::find(_observers.begin(), _observers.end(), &client) == _observers.end()); + _observers.push_back(&client); + if (_channel.IsOpen() == true) { + client.Opened(); + } + _adminLock.Unlock(); + } + void Unregister(CLIENT& client) + { + _adminLock.Lock(); + typename std::list::iterator index(std::find(_observers.begin(), _observers.end(), &client)); + if (index != _observers.end()) { + _observers.erase(index); + } + FactoryImpl::Instance().Revoke(&client); + _adminLock.Unlock(); + } + + void Submit(const WPEFramework::Core::ProxyType& message) + { + _channel.Submit(message); + } + bool IsSuspended() const + { + return (_channel.IsSuspended()); + } + uint32_t Initialize() + { + return (Open(0)); + } + void Deinitialize() + { + Close(); + } + bool IsOpen() + { + return (_channel.IsOpen() == true); + } + + protected: + void StateChange() + { + _adminLock.Lock(); + typename std::list::iterator index(_observers.begin()); + while (index != _observers.end()) { + if (_channel.IsOpen() == true) { + (*index)->Opened(); + } + else { + (*index)->Closed(); + } + index++; + } + _adminLock.Unlock(); + } + bool Open(const uint32_t waitTime) + { + bool result = true; + if (_channel.IsClosed() == true) { + result = (_channel.Open(waitTime) == WPEFramework::Core::ERROR_NONE); + } + return (result); + } + void Close() + { + _channel.Close(WPEFramework::Core::infinite); + } + + private: + int32_t Inbound(const WPEFramework::Core::ProxyType& inbound) + { + int32_t result = WPEFramework::Core::ERROR_UNAVAILABLE; + _adminLock.Lock(); + typename std::list::iterator index(_observers.begin()); + while ((result != WPEFramework::Core::ERROR_NONE) && (index != _observers.end())) { + result = (*index)->Submit(inbound); + index++; + } + _adminLock.Unlock(); + + return (result); + } + + private: + WPEFramework::Core::CriticalSection _adminLock; + ChannelImpl _channel; + mutable std::atomic _sequence; + std::list _observers; + }; + + class IEventHandler { + public: + virtual int32_t ValidateResponse(const WPEFramework::Core::ProxyType& jsonResponse, bool& enabled) = 0; + virtual int32_t Dispatch(const string& eventName, const WPEFramework::Core::ProxyType& jsonResponse) = 0; + virtual ~IEventHandler() = default; + }; + + template + class Transport { + private: + using Channel = CommunicationChannel; + using Entry = typename CommunicationChannel::Entry; + using PendingMap = std::unordered_map; + using EventMap = std::map; + typedef std::function& jsonResponse, bool& enabled)> EventResponseValidatioionFunction; + + class Job : public WPEFramework::Core::IDispatch { + protected: + Job(const WPEFramework::Core::ProxyType& inbound, class Transport* parent) + : _inbound(inbound) + , _parent(parent) + { + } + + public: + Job() = delete; + Job(const Job&) = delete; + Job& operator=(const Job&) = delete; + + ~Job() = default; + + public: + static WPEFramework::Core::ProxyType Create(const WPEFramework::Core::ProxyType& inbound, class Transport* parent); + + void Dispatch() override + { + _parent->Inbound(_inbound); + } + + private: + const WPEFramework::Core::ProxyType _inbound; + class Transport* _parent; + }; + + protected: + static constexpr uint32_t DefaultWaitTime = 10000; + + inline void Announce() { + _channel->Register(*this); + } + + private: + static constexpr const TCHAR* PathPrefix = _T("/"); + public: + Transport() = delete; + Transport(const Transport&) = delete; + Transport& operator=(Transport&) = delete; + Transport(const WPEFramework::Core::URL& url, const uint32_t waitTime) + : _adminLock() + , _connectId(WPEFramework::Core::NodeId(url.Host().Value().c_str(), url.Port().Value())) + , _channel(Channel::Instance(_connectId, ((url.Path().Value().rfind(PathPrefix, 0) == 0) ? url.Path().Value() : string(PathPrefix + url.Path().Value())), url.Query().Value(), true)) + , _eventHandler(nullptr) + , _pendingQueue() + , _scheduledTime(0) + , _waitTime(waitTime) + { + _channel->Register(*this); + } + + virtual ~Transport() + { + _channel->Unregister(*this); + + for (auto& element : _pendingQueue) { + element.second.Abort(element.first); + } + } + + public: + inline bool IsOpen() + { + return _channel->IsOpen(); + } + + void Revoke(const string& eventName) + { + _adminLock.Lock(); + _eventMap.erase(eventName); + _adminLock.Unlock(); + } + + void SetEventHandler(IEventHandler* eventHandler) + { + _eventHandler = eventHandler; + } + + template + int32_t Invoke(const string& method, const PARAMETERS& parameters, RESPONSE& response) + { + Entry slot; + uint32_t id = _channel->Sequence(); + int32_t result = Send(method, parameters, id); + if (result == WPEFramework::Core::ERROR_NONE) { + result = WaitForResponse(id, response, _waitTime); + } + + return (FireboltErrorValue(result)); + } + + template + int32_t Subscribe(const string& eventName, const string& parameters, RESPONSE& response) + { + Entry slot; + uint32_t id = _channel->Sequence(); + int32_t result = Send(eventName, parameters, id); + if (result == WPEFramework::Core::ERROR_NONE) { + _adminLock.Lock(); + _eventMap.emplace(std::piecewise_construct, + std::forward_as_tuple(eventName), + std::forward_as_tuple(~0)); + _adminLock.Unlock(); + + result = WaitForEventResponse(id, eventName, response, _waitTime); + } + + return (FireboltErrorValue(result)); + } + + int32_t Unsubscribe(const string& eventName, const string& parameters) + { + Revoke(eventName); + Entry slot; + uint32_t id = _channel->Sequence(); + int32_t result = Send(eventName, parameters, id); + + return (FireboltErrorValue(result)); + } + + private: + friend Channel; + inline bool IsEvent(const uint32_t id, string& eventName) + { + _adminLock.Lock(); + for (auto& event : _eventMap) { + if (event.second == id) { + eventName = event.first; + break; + } + } + _adminLock.Unlock(); + return (eventName.empty() != true); + } + uint64_t Timed() + { + uint64_t result = ~0; + uint64_t currentTime = WPEFramework::Core::Time::Now().Ticks(); + + // Lets see if some callback are expire. If so trigger and remove... + _adminLock.Lock(); + + typename PendingMap::iterator index = _pendingQueue.begin(); + + while (index != _pendingQueue.end()) { + + if (index->second.Expired(index->first, currentTime, result) == true) { + index = _pendingQueue.erase(index); + } + else { + index++; + } + } + _scheduledTime = (result != static_cast(~0) ? result : 0); + + _adminLock.Unlock(); + + return (_scheduledTime); + } + + virtual void Opened() + { + // Nice to know :-) + } + + void Closed() + { + // Abort any in progress RPC command: + _adminLock.Lock(); + + // See if we issued anything, if so abort it.. + while (_pendingQueue.size() != 0) { + + _pendingQueue.begin()->second.Abort(_pendingQueue.begin()->first); + _pendingQueue.erase(_pendingQueue.begin()); + } + + _adminLock.Unlock(); + } + + int32_t Submit(const WPEFramework::Core::ProxyType& inbound) + { + int32_t result = WPEFramework::Core::ERROR_UNAVAILABLE; + WPEFramework::Core::ProxyType job = WPEFramework::Core::ProxyType(WPEFramework::Core::ProxyType::Create(inbound, this)); + WPEFramework::Core::IWorkerPool::Instance().Submit(job); + return result; + } + + int32_t Inbound(const WPEFramework::Core::ProxyType& inbound) + { + int32_t result = WPEFramework::Core::ERROR_INVALID_SIGNATURE; + + ASSERT(inbound.IsValid() == true); + + if ((inbound->Id.IsSet() == true) && (inbound->Result.IsSet() || inbound->Error.IsSet())) { + // Looks like this is a response.. + ASSERT(inbound->Parameters.IsSet() == false); + ASSERT(inbound->Designator.IsSet() == false); + + _adminLock.Lock(); + + // See if we issued this.. + typename PendingMap::iterator index = _pendingQueue.find(inbound->Id.Value()); + + if (index != _pendingQueue.end()) { + + if (index->second.Signal(inbound) == true) { + _pendingQueue.erase(index); + } + + result = WPEFramework::Core::ERROR_NONE; + _adminLock.Unlock(); + } else { + _adminLock.Unlock(); + string eventName; + if (IsEvent(inbound->Id.Value(), eventName)) { + _eventHandler->Dispatch(eventName, inbound); + } + + } + } + + return (result); + } + + + template + int32_t Send(const string& method, const PARAMETERS& parameters, const uint32_t& id) + { + int32_t result = WPEFramework::Core::ERROR_UNAVAILABLE; + + if ((_channel.IsValid() == true) && (_channel->IsSuspended() == true)) { + result = WPEFramework::Core::ERROR_ASYNC_FAILED; + } + else if (_channel.IsValid() == true) { + + result = WPEFramework::Core::ERROR_ASYNC_FAILED; + + WPEFramework::Core::ProxyType message(Channel::Message()); + message->Id = id; + message->Designator = method; + ToMessage(parameters, message); + + _adminLock.Lock(); + + typename std::pair< typename PendingMap::iterator, bool> newElement = + _pendingQueue.emplace(std::piecewise_construct, + std::forward_as_tuple(id), + std::forward_as_tuple()); + ASSERT(newElement.second == true); + + if (newElement.second == true) { + + _adminLock.Unlock(); + + _channel->Submit(WPEFramework::Core::ProxyType(message)); + + message.Release(); + result = WPEFramework::Core::ERROR_NONE; + } + } + return result; + } + + template + int32_t WaitForResponse(const uint32_t& id, RESPONSE& response, const uint32_t waitTime) + { + int32_t result = WPEFramework::Core::ERROR_TIMEDOUT; + _adminLock.Lock(); + typename PendingMap::iterator index = _pendingQueue.find(id); + Entry& slot(index->second); + _adminLock.Unlock(); + + if (slot.WaitForResponse(waitTime) == true) { + WPEFramework::Core::ProxyType jsonResponse = slot.Response(); + + // See if we have a jsonResponse, maybe it was just the connection + // that closed? + if (jsonResponse.IsValid() == true) { + if (jsonResponse->Error.IsSet() == true) { + result = jsonResponse->Error.Code.Value(); + } + else { + result = WPEFramework::Core::ERROR_NONE; + if ((jsonResponse->Result.IsSet() == true) + && (jsonResponse->Result.Value().empty() == false)) { + FromMessage((INTERFACE*)&response, *jsonResponse); + } + } + } + } else { + result = WPEFramework::Core::ERROR_TIMEDOUT; + } + _adminLock.Lock(); + _pendingQueue.erase(id); + _adminLock.Unlock(); + return result; + } + + static constexpr uint32_t WAITSLOT_TIME = 100; + template + int32_t WaitForEventResponse(const uint32_t& id, const string& eventName, RESPONSE& response, const uint32_t waitTime) + { + int32_t result = WPEFramework::Core::ERROR_TIMEDOUT; + _adminLock.Lock(); + typename PendingMap::iterator index = _pendingQueue.find(id); + Entry& slot(index->second); + _adminLock.Unlock(); + + uint8_t waiting = waitTime; + do { + uint32_t waitSlot = (waiting > WAITSLOT_TIME ? WAITSLOT_TIME : waiting); + if (slot.WaitForResponse(waitSlot) == true) { + WPEFramework::Core::ProxyType jsonResponse = slot.Response(); + + // See if we have a jsonResponse, maybe it was just the connection + // that closed? + if (jsonResponse.IsValid() == true) { + if (jsonResponse->Error.IsSet() == true) { + result = jsonResponse->Error.Code.Value(); + } else { + if ((jsonResponse->Result.IsSet() == true) + && (jsonResponse->Result.Value().empty() == false)) { + result = WPEFramework::Core::ERROR_NONE; + bool enabled; + result = _eventHandler->ValidateResponse(jsonResponse, enabled); + if (result == WPEFramework::Core::ERROR_NONE) { + FromMessage((INTERFACE*)&response, *jsonResponse); + if (enabled) { + _adminLock.Lock(); + typename EventMap::iterator index = _eventMap.find(eventName); + if (index != _eventMap.end()) { + index->second = id; + } + _adminLock.Unlock(); + } + } + } + } + } + } else { + result = WPEFramework::Core::ERROR_TIMEDOUT; + } + waiting -= (waiting == WPEFramework::Core::infinite ? 0 : waitSlot); + } while ((result != WPEFramework::Core::ERROR_NONE) && (waiting > 0 )); + _adminLock.Lock(); + _pendingQueue.erase(id); + _adminLock.Unlock(); + + return result; + } + + public: + void FromMessage(WPEFramework::Core::JSON::IElement* response, const WPEFramework::Core::JSONRPC::Message& message) const + { + response->FromString(message.Result.Value()); + } + + void FromMessage(WPEFramework::Core::JSON::IMessagePack* response, const WPEFramework::Core::JSONRPC::Message& message) const + { + string value = message.Result.Value(); + std::vector result(value.begin(), value.end()); + response->FromBuffer(result); + } + + + private: + + void ToMessage(const string& parameters, WPEFramework::Core::ProxyType& message) const + { + if (parameters.empty() != true) { + message->Parameters = parameters; + } + } + + template + void ToMessage(PARAMETERS& parameters, WPEFramework::Core::ProxyType& message) const + { + ToMessage((INTERFACE*)(¶meters), message); + return; + } + + void ToMessage(WPEFramework::Core::JSON::IMessagePack* parameters, WPEFramework::Core::ProxyType& message) const + { + std::vector values; + parameters->ToBuffer(values); + if (values.empty() != true) { + string strValues(values.begin(), values.end()); + message->Parameters = strValues; + } + return; + } + + void ToMessage(WPEFramework::Core::JSON::IElement* parameters, WPEFramework::Core::ProxyType& message) const + { + string values; + parameters->ToString(values); + if (values.empty() != true) { + message->Parameters = values; + } + return; + } + + int32_t FireboltErrorValue(const uint32_t error) + { + + int32_t fireboltError = error; + switch (error) { + case WPEFramework::Core::ERROR_NONE: + fireboltError = FireboltSDKErrorNone; + break; + case WPEFramework::Core::ERROR_GENERAL: + fireboltError = FireboltSDKErrorGeneral; + break; + case WPEFramework::Core::ERROR_UNAVAILABLE: + fireboltError = FireboltSDKErrorUnavailable; + break; + case WPEFramework::Core::ERROR_TIMEDOUT: + fireboltError = FireboltSDKErrorTimedout; + break; + default: + break; + } + + return fireboltError; + } + + private: + WPEFramework::Core::CriticalSection _adminLock; + WPEFramework::Core::NodeId _connectId; + WPEFramework::Core::ProxyType _channel; + IEventHandler* _eventHandler; + PendingMap _pendingQueue; + EventMap _eventMap; + uint64_t _scheduledTime; + uint32_t _waitTime; + }; +} diff --git a/languages/c/src/shared/src/Types.cpp b/languages/c/src/shared/src/Types.cpp new file mode 100644 index 00000000..93f6a8d8 --- /dev/null +++ b/languages/c/src/shared/src/Types.cpp @@ -0,0 +1,40 @@ +/* + * Copyright 2023 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 + */ + +#include "Module.h" +#include "types.h" +#include "TypesPriv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// String Type Handler Interfaces +const char* Firebolt_String(Firebolt_String_t handle) +{ + return ((reinterpret_cast(handle))->Value().c_str()); +} + +void Firebolt_String_Release(Firebolt_String_t handle) +{ + delete reinterpret_cast(handle); +} + +#ifdef __cplusplus +} +#endif diff --git a/languages/c/src/shared/src/TypesPriv.h b/languages/c/src/shared/src/TypesPriv.h new file mode 100644 index 00000000..6e365ec7 --- /dev/null +++ b/languages/c/src/shared/src/TypesPriv.h @@ -0,0 +1,56 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include + +namespace FireboltSDK { +namespace JSON { +class String : public WPEFramework::Core::JSON::String { + using Base = WPEFramework::Core::JSON::String; + public: + String() + : Base() + , _value() + { + } + String(const char value[]) + : Base(value) + , _value(value) + { + } + String& operator=(const char RHS[]) + { + Base::operator = (RHS); + _value = RHS; + return (*this); + } + + public: + const string& Value() const + { + _value = Base::Value(); + return _value; + } + + private: + mutable std::string _value; + }; +} +} diff --git a/languages/c/src/shared/src/firebolt.cpp b/languages/c/src/shared/src/firebolt.cpp new file mode 100644 index 00000000..90c54c5c --- /dev/null +++ b/languages/c/src/shared/src/firebolt.cpp @@ -0,0 +1,39 @@ +/* + * Copyright 2023 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 + */ + +#include "FireboltSDK.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +int32_t FireboltSDK_Initialize(char* configLine) { + FireboltSDK::Accessor::Instance(configLine); + return FireboltSDKErrorNone; +} + +int32_t FireboltSDK_Deinitialize(void) { + FireboltSDK::Accessor::Dispose(); + return FireboltSDKErrorNone; +} + +#ifdef __cplusplus +} +#endif diff --git a/languages/c/src/shared/test/CMakeLists.txt b/languages/c/src/shared/test/CMakeLists.txt new file mode 100644 index 00000000..21ee1f55 --- /dev/null +++ b/languages/c/src/shared/test/CMakeLists.txt @@ -0,0 +1,86 @@ +# Copyright 2023 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 + +cmake_minimum_required(VERSION 3.3) + +project(FireboltSDKTests) +project_version(1.0.0) + +set(TESTLIB ${PROJECT_NAME}) + +message("Setup ${TESTLIB} v${PROJECT_VERSION}") + +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +find_package(${NAMESPACE}Core CONFIG REQUIRED) + +add_library(${TESTLIB} STATIC OpenRPCTests.cpp) + +target_link_libraries(${TESTLIB} + PUBLIC + ${NAMESPACE}Core::${NAMESPACE}Core + ${FIREBOLT_NAMESPACE}SDK +) + +target_include_directories(${TESTLIB} + PRIVATE + $ + $ +) + +set_target_properties(${TESTLIB} PROPERTIES + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED YES + LINK_WHAT_YOU_USE TRUE + FRAMEWORK FALSE +) + +install( + TARGETS ${TESTLIB} EXPORT ${TESTLIB}Targets + LIBRARY DESTINATION lib COMPONENT libs + PUBLIC_HEADER DESTINATION include/${FIREBOLT_NAMESPACE}Test COMPONENT devel # headers for mac (note the different component -> different package) + INCLUDES DESTINATION include/${FIREBOLT_NAMESPACE}Test # headers +) + +InstallCMakeConfig(TARGETS ${TESTLIB}) +InstallCMakeConfigs(TARGET ${TESTLIB} DESTINATION ${FIREBOLT_NAMESPACE}) +InstallHeaders(TARGET ${TESTLIB} HEADERS . NAMESPACE ${FIREBOLT_NAMESPACE} DESTINATION FireboltTest) +InstallLibraries(TARGET ${TESTLIB} STATIC LIBRARIES ${TESTLIB} DESTINATION ${FIREBOLT_NAMESPACE}) + +set(TESTAPP "FireboltSDKTestApp") + +message("Setup ${TESTAPP}") + +add_executable(${TESTAPP} Main.c) + +target_link_libraries(${TESTAPP} + PRIVATE + ${TESTLIB} +) + +target_include_directories(${TESTAPP} + PRIVATE + $ + $ +) + +add_custom_command( + TARGET ${TESTAPP} + POST_BUILD + COMMENT "=================== Installing TestApp ======================" + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/${FIREBOLT_NAMESPACE}/usr/bin + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/${TESTAPP} ${CMAKE_BINARY_DIR}/${FIREBOLT_NAMESPACE}/usr/bin +) + diff --git a/languages/c/src/shared/test/Main.c b/languages/c/src/shared/test/Main.c new file mode 100644 index 00000000..6161792e --- /dev/null +++ b/languages/c/src/shared/test/Main.c @@ -0,0 +1,45 @@ +/* + * Copyright 2023 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 + */ + +#include "OpenRPCCTests.h" + +int __cnt = 0; +int __pass = 0; + +int TotalTests = 0; +int TotalTestsPassed = 0; + +int main() +{ + test_firebolt_create_instance(); + test_firebolt_main(); + + // Calling C function sequences + printf("%s:%s:%d Calling C function tests\n", __FILE__, __func__, __LINE__); + EXECUTE("test_properties_get_device_id", test_properties_get_device_id); + EXECUTE("test_properties_set", test_properties_set); + EXECUTE("test_eventregister_by_providing_callback", test_eventregister_by_providing_callback); + EXECUTE("test_eventregister", test_eventregister); + EXECUTE("test_eventregister_with_same_callback", test_eventregister_with_same_callback); + EXECUTE("test_string_set_get_value", test_string_set_get_value); + + test_firebolt_dispose_instance(); + + printf("TOTAL: %i tests; %i PASSED, %i FAILED\n", TotalTests, TotalTestsPassed, (TotalTests - TotalTestsPassed)); +} + diff --git a/languages/c/src/shared/test/Module.cpp b/languages/c/src/shared/test/Module.cpp new file mode 100644 index 00000000..d63badc4 --- /dev/null +++ b/languages/c/src/shared/test/Module.cpp @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 + */ + +#include "Module.h" + +MODULE_NAME_DECLARATION(BUILD_REFERENCE) diff --git a/languages/c/src/shared/test/Module.h b/languages/c/src/shared/test/Module.h new file mode 100644 index 00000000..a147ff75 --- /dev/null +++ b/languages/c/src/shared/test/Module.h @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#ifndef MODULE_NAME +#define MODULE_NAME OpenRPCTestApp +#endif + +#include + +#undef EXTERNAL +#define EXTERNAL diff --git a/languages/c/src/shared/test/OpenRPCCTests.h b/languages/c/src/shared/test/OpenRPCCTests.h new file mode 100644 index 00000000..936a1ff9 --- /dev/null +++ b/languages/c/src/shared/test/OpenRPCCTests.h @@ -0,0 +1,43 @@ +/* + * Copyright 2023 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 + */ + +#ifndef OPENRPC_C_TESTS_H +#define OPENRPC_C_TESTS_H + +#include "TestUtils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void test_firebolt_create_instance(); +void test_firebolt_dispose_instance(); + +int32_t test_firebolt_main(); +int32_t test_properties_get_device_id(); +int32_t test_properties_set(); +int32_t test_eventregister(); +int32_t test_eventregister_with_same_callback(); +int32_t test_eventregister_by_providing_callback(); +int32_t test_string_set_get_value(); + +#ifdef __cplusplus +} +#endif + +#endif // OPENRPC_C_TESTS_H diff --git a/languages/c/src/shared/test/OpenRPCTests.cpp b/languages/c/src/shared/test/OpenRPCTests.cpp new file mode 100644 index 00000000..03fd2645 --- /dev/null +++ b/languages/c/src/shared/test/OpenRPCTests.cpp @@ -0,0 +1,504 @@ +/* + * Copyright 2023 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 + */ + +#include "Module.h" +#include "OpenRPCTests.h" +#include "OpenRPCCTests.h" + +namespace WPEFramework { + +ENUM_CONVERSION_BEGIN(::JsonValue::type) + + { JsonValue::type::EMPTY, _TXT("empty") }, + { JsonValue::type::BOOLEAN, _TXT("boolean") }, + { JsonValue::type::NUMBER, _TXT("number") }, + { JsonValue::type::STRING, _TXT("string") }, + +ENUM_CONVERSION_END(::JsonValue::type) + +ENUM_CONVERSION_BEGIN(TestEnum) + { TestEnum::Test1, _TXT("Test1ValueCheck") }, + { TestEnum::Test2, _TXT("Test2ValueCheck") }, + { TestEnum::Test3, _TXT("Test3ValueCheck") }, + { TestEnum::Test4, _TXT("Test4ValueCheck") }, +ENUM_CONVERSION_END(TestEnum) +} + +typedef void (*OnNotifyDeviceNameChanged)(const void* userData, const char* data); +static void NotifyEvent(const void* userData, const char* data) +{ + printf("NotifyEvent data : %s\n", data); +} + +namespace FireboltSDK { + Tests::Tests() + { + _functionMap.emplace(std::piecewise_construct, std::forward_as_tuple("SubscribeEventWithMultipleCallback"), + std::forward_as_tuple(&SubscribeEventWithMultipleCallback)); + _functionMap.emplace(std::piecewise_construct, std::forward_as_tuple("SubscribeEventwithSameCallback"), + std::forward_as_tuple(&SubscribeEventwithSameCallback)); + _functionMap.emplace(std::piecewise_construct, std::forward_as_tuple("SubscribeEvent"), + std::forward_as_tuple(&SubscribeEvent)); + + _functionMap.emplace(std::piecewise_construct, std::forward_as_tuple("Set UnKnown Method"), + std::forward_as_tuple(&SetUnKnownMethod)); + _functionMap.emplace(std::piecewise_construct, std::forward_as_tuple("Set LifeCycle Close"), + std::forward_as_tuple(&SetLifeCycleClose)); + + _functionMap.emplace(std::piecewise_construct, std::forward_as_tuple("Get UnKnown Method"), + std::forward_as_tuple(&GetUnKnownMethod)); + _functionMap.emplace(std::piecewise_construct, std::forward_as_tuple("Get Device Version"), + std::forward_as_tuple(&GetDeviceVersion)); + _functionMap.emplace(std::piecewise_construct, std::forward_as_tuple("Get Device Id"), + std::forward_as_tuple(&GetDeviceId)); + } + + /* static */ void Tests::PrintJsonObject(const JsonObject::Iterator& iterator) + { + JsonObject::Iterator index = iterator; + while (index.Next() == true) { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "Element [%s]: <%s> = \"%s\"\n", + index.Label(), + WPEFramework::Core::EnumerateType(index.Current().Content()).Data(), + index.Current().Value().c_str()); + } + } + + /* static */ int32_t Tests::GetDeviceId() + { + const string method = _T("device.id"); + WPEFramework::Core::ProxyType response; + int32_t status = FireboltSDK::Properties::Get(method, response); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status == FireboltSDKErrorNone) { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), "DeviceId : %s", response->Value().c_str()); + } else { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Get %s status = %d\n", method.c_str(), status); + } + + return status; + } + + /*static */ int32_t Tests::GetDeviceVersion() + { + const string method = _T("device.version"); + WPEFramework::Core::ProxyType response; + int32_t status = FireboltSDK::Properties::Get(method, response); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status == FireboltSDKErrorNone) { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), "DeviceVersion"); + PrintJsonObject(response->Variants()); + } else { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Get %s status = %d", method.c_str(), status); + } + + return status; + } + + /* static */ int32_t Tests::GetUnKnownMethod() + { + const string method = _T("get.unknownMethod"); + WPEFramework::Core::ProxyType response; + int32_t status = FireboltSDK::Properties::Get(method, response); + + EXPECT_NE(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Get %s status = %d\n", method.c_str(), status); + } + + return status; + } + + /* static */ int32_t Tests::SetLifeCycleClose() + { + const string method = _T("lifecycle.close"); + JsonObject parameters; + parameters["reason"] = "remoteButton"; + int32_t status = FireboltSDK::Properties::Set(method, parameters); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Set %s status = %d\n", method.c_str(), status); + } + + return status; + } + + /* static */ int32_t Tests::SetUnKnownMethod() + { + const string method = _T("set.unknownMethod"); + JsonObject parameters; + int32_t status = FireboltSDK::Properties::Set(method, parameters); + + EXPECT_NE(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Set %s status = %d", method.c_str(), status); + } + + return status; + } + + static void deviceNameChangeCallback(void* userCB, const void* userData, void* response) + { + WPEFramework::Core::ProxyType& jsonResponse = *(reinterpret_cast*>(response)); + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), "Received a new event: %s", jsonResponse->Value().c_str()); + FireboltSDK::Tests::EventControl* eventControl = reinterpret_cast(const_cast(userData)); + OnNotifyDeviceNameChanged notifyDeviceNameChanged = reinterpret_cast(userCB); + notifyDeviceNameChanged(userData, jsonResponse->Value().c_str()); + eventControl->NotifyEvent(); + jsonResponse.Release(); + } + + /* static */ int32_t Tests::SubscribeEvent() + { + FireboltSDK::Tests::EventControl* eventControl = new FireboltSDK::Tests::EventControl("EventControl"); + const string eventName = _T("device.Name"); + const void* userdata = static_cast(eventControl); + + eventControl->ResetEvent(); + + JsonObject jsonParameters; + int32_t status = Properties::Subscribe(eventName, jsonParameters, deviceNameChangeCallback,reinterpret_cast(NotifyEvent), userdata); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), + "Set %s status = %d", eventName.c_str(), status); + } else { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "%s Yes registered successfully, Waiting for event...", __func__); + + eventControl->WaitForEvent(WPEFramework::Core::infinite); + } + + EXPECT_EQ(Properties::Unsubscribe(eventName, reinterpret_cast(NotifyEvent)), FireboltSDKErrorNone); + delete eventControl; + + return status; + } + + /* static */ int32_t Tests::SubscribeEventwithSameCallback() + { + FireboltSDK::Tests::EventControl* eventControl = new FireboltSDK::Tests::EventControl("EventControl"); + const string eventName = _T("device.Name"); + const void* userdata = static_cast(eventControl); + + eventControl->ResetEvent(); + + JsonObject jsonParameters; + int32_t status = Properties::Subscribe(eventName, jsonParameters, deviceNameChangeCallback,reinterpret_cast(NotifyEvent), userdata); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), + "Set %s status = %d", eventName.c_str(), status); + } else { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "%s Yes registered successfully", __func__); + + status = Properties::Subscribe(eventName, jsonParameters, deviceNameChangeCallback, reinterpret_cast(NotifyEvent), userdata); + EXPECT_EQ(status, FireboltSDKErrorInUse); + if (status == FireboltSDKErrorInUse) { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "%s Yes this device.name event is already registered with same callback", __func__); + } + status = ((status == FireboltSDKErrorInUse) ? FireboltSDKErrorNone : status); + } + + EXPECT_EQ(Properties::Unsubscribe(eventName, reinterpret_cast(NotifyEvent)), FireboltSDKErrorNone); + delete eventControl; + + return status; + } + + + static void NotifyEvent1(const void* userData, const char* data) + { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "NotifyEvent1 data : %s", data); + if (userData) { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "NotifyEvent1 userData : %s\n", reinterpret_cast(userData)->Name().c_str()); + } + } + static void NotifyEvent2(const void* userData, const char* data) + { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "NotifyEvent2 data : %s", data); + if (userData) { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "NotifyEvent2 userData : %s\n", reinterpret_cast(userData)->Name().c_str()); + } + } + + template + /* static */ int32_t Tests::SubscribeEventForC(const string& eventName, JsonObject& jsonParameters, CALLBACK& callbackFunc, void* usercb, const void* userdata) + { + return Properties::Subscribe(eventName, jsonParameters, callbackFunc, usercb, userdata); + } + + static void deviceNameChangeMultipleCallback(void* userCB, const void* userData, void* response) + { + WPEFramework::Core::ProxyType& jsonResponse = *(reinterpret_cast*>(response)); + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "Received a new event from deviceNameChangeMultipleCallback: %s", jsonResponse->Value().c_str()); + FireboltSDK::Tests::EventControl* eventControl = reinterpret_cast(const_cast(userData)); + OnNotifyDeviceNameChanged notifyDeviceNameChanged = reinterpret_cast(userCB); + notifyDeviceNameChanged(userData, jsonResponse->Value().c_str()); + + eventControl->NotifyEvent(); + jsonResponse.Release(); + } + + /* static */ int32_t Tests::SubscribeEventWithMultipleCallback() + { + FireboltSDK::Tests::EventControl* eventControl1 = new FireboltSDK::Tests::EventControl("EventControl1"); + const string eventName = _T("device.Name"); + const void* userdata = static_cast(eventControl1); + + eventControl1->ResetEvent(); + + JsonObject jsonParameters; + int32_t status = Properties::Subscribe(eventName, jsonParameters, deviceNameChangeMultipleCallback, reinterpret_cast(NotifyEvent1), userdata); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), + "Set %s status = %d", eventName.c_str(), status); + } else { + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "%s Yes registered successfully, Waiting for event...", __func__); + } + + if (status == FireboltSDKErrorNone) { + FireboltSDK::Tests::EventControl* eventControl2 = new FireboltSDK::Tests::EventControl("EventControl2"); + userdata = static_cast(eventControl2); + + status = Properties::Subscribe(eventName, jsonParameters, deviceNameChangeMultipleCallback, reinterpret_cast(NotifyEvent2), userdata); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(Logger::Category::OpenRPC, Logger::Module(), "Set %s status = %d", eventName.c_str(), status); + } else { + status = Properties::Subscribe(eventName, jsonParameters, deviceNameChangeMultipleCallback, reinterpret_cast(NotifyEvent2), userdata); + EXPECT_EQ(status, FireboltSDKErrorInUse); + status = ((status == FireboltSDKErrorInUse) ? FireboltSDKErrorNone : status); + + FIREBOLT_LOG_INFO(Logger::Category::OpenRPC, Logger::Module(), + "%s Yes registered second callback also successfully, waiting for events...\n", __func__); + + eventControl1->WaitForEvent(WPEFramework::Core::infinite); + eventControl2->WaitForEvent(WPEFramework::Core::infinite); + } + EXPECT_EQ(Properties::Unsubscribe(eventName, reinterpret_cast(NotifyEvent2)), FireboltSDKErrorNone); + delete eventControl2; + } + EXPECT_EQ(Properties::Unsubscribe(eventName, reinterpret_cast(NotifyEvent1)), FireboltSDKErrorNone); + + delete eventControl1; + return status; + } + +} + +#ifdef __cplusplus +extern "C" { +#endif + +void test_firebolt_create_instance() +{ + const std::string config = _T("{\ + \"waitTime\": 1000,\ + \"logLevel\": \"Info\",\ + \"workerPool\":{\ + \"queueSize\": 8,\ + \"threadCount\": 3\ + },\ + \"wsUrl\": \"ws://127.0.0.1:9998\"\ +}"); + FireboltSDK::Accessor::Instance(config); +} + +void test_firebolt_dispose_instance() +{ + FireboltSDK::Accessor::Dispose(); +} + +int32_t test_firebolt_main() +{ + return FireboltSDK::Tests::Main(); +} + +int32_t test_properties_get_device_id() +{ + const string method = _T("device.id"); + WPEFramework::Core::ProxyType response; + int32_t status = FireboltSDK::Properties::Get(method, response); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status == FireboltSDKErrorNone) { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "DeviceId : %s", response->Value().c_str()); + } else { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "Get %s status = %d", method.c_str(), status); + } + + return status; +} + +int32_t test_properties_set() +{ + const string method = _T("lifecycle.close"); + JsonObject parameters; + parameters["reason"] = "remoteButton"; + int32_t status = FireboltSDK::Properties::Set(method, parameters); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "Set %s status = %d", method.c_str(), status); + } + + return status; +} + +static void deviceNameChangeCallbackForC(void* userCB, const void* userData, void* response) +{ + WPEFramework::Core::ProxyType& jsonResponse = *(reinterpret_cast*>(response)); + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "Received a new event--->: %s", jsonResponse->Value().c_str()); + + FireboltSDK::Tests::EventControl* eventControl = reinterpret_cast(const_cast(userData)); + eventControl->NotifyEvent(); + jsonResponse.Release(); +} + +int32_t test_eventregister() +{ + FireboltSDK::Tests::EventControl* eventControl = new FireboltSDK::Tests::EventControl(); + const string eventName = _T("device.Name"); + const void* userdata = static_cast(eventControl); + + eventControl->ResetEvent(); + + JsonObject jsonParameters; + int32_t status = FireboltSDK::Properties::Subscribe(eventName, jsonParameters, deviceNameChangeCallbackForC, reinterpret_cast(NotifyEvent), userdata); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "%s Set %s status = %d", __func__, eventName.c_str(), status); + } else { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "%s Yes registered successfully, Waiting for event...", __func__); + eventControl->WaitForEvent(WPEFramework::Core::infinite); + } + + delete eventControl; + EXPECT_EQ(FireboltSDK::Properties::Unsubscribe(eventName, reinterpret_cast(NotifyEvent)), FireboltSDKErrorNone); + + return status; +} + +int32_t test_eventregister_with_same_callback() +{ + FireboltSDK::Tests::EventControl* eventControl = new FireboltSDK::Tests::EventControl(); + const string eventName = _T("device.Name"); + const void* userdata = static_cast(eventControl); + + eventControl->ResetEvent(); + + JsonObject jsonParameters; + int32_t status = FireboltSDK::Properties::Subscribe(eventName, jsonParameters, deviceNameChangeCallbackForC, reinterpret_cast(NotifyEvent), userdata); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "%s Set %s status = %d", __func__, eventName.c_str(), status); + } else { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "%s Yes registered successfully", __func__); + + status = FireboltSDK::Properties::Subscribe(eventName, jsonParameters, deviceNameChangeCallbackForC, reinterpret_cast(NotifyEvent), userdata); + EXPECT_EQ(status, FireboltSDKErrorInUse); + if (status == FireboltSDKErrorInUse) { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "%s Yes this device.name event is already registered with same callback", __func__); + } + status = ((status == FireboltSDKErrorInUse) ? FireboltSDKErrorNone : status); + } + + delete eventControl; + EXPECT_EQ(FireboltSDK::Properties::Unsubscribe(eventName, reinterpret_cast(NotifyEvent)), FireboltSDKErrorNone); + + return status; +} +int32_t test_eventregister_by_providing_callback() +{ + FireboltSDK::Tests::EventControl* eventControl = new FireboltSDK::Tests::EventControl(); + + const string eventName = _T("device.Name"); + const void* userdata = static_cast(eventControl); + + eventControl->ResetEvent(); + + JsonObject jsonParameters; + int32_t status = FireboltSDK::Tests::SubscribeEventForC(eventName, jsonParameters, deviceNameChangeCallbackForC, reinterpret_cast(NotifyEvent), userdata); + + EXPECT_EQ(status, FireboltSDKErrorNone); + if (status != FireboltSDKErrorNone) { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "%s Set %s status = %d", __func__, eventName.c_str(), status); + } else { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, "ctest", + "%s Yes registered successfully, Waiting for event...", __func__); + eventControl->WaitForEvent(WPEFramework::Core::infinite); + } + + delete eventControl; + EXPECT_EQ(FireboltSDK::Properties::Unsubscribe(eventName, reinterpret_cast(NotifyEvent)), FireboltSDKErrorNone); + return status; +} + +#include "TypesPriv.h" +int32_t test_string_set_get_value() +{ + int32_t status = FireboltSDKErrorNone; + FireboltSDK::JSON::String* str = new FireboltSDK::JSON::String(); + WPEFramework::Core::JSON::String wpeJsonStr("TestString"); + Firebolt_String_t handle = reinterpret_cast(str); + + const char* value = Firebolt_String(handle); + EXPECT_EQ(strncmp(value, str->Value().c_str(), str->Value().length()), 0); + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, "ctest", + " ---> type name = %s %s", str->Value().c_str(), value); + + WPEFramework::Core::JSON::EnumType<::TestEnum> testEnum = Test4; + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, "ctest", + " EnumTest = %d %s", testEnum.Value(), testEnum.Data()); + Firebolt_String_Release(handle); + return status; +} + +#ifdef __cplusplus +} +#endif diff --git a/languages/c/src/shared/test/OpenRPCTests.h b/languages/c/src/shared/test/OpenRPCTests.h new file mode 100644 index 00000000..8eb7fd30 --- /dev/null +++ b/languages/c/src/shared/test/OpenRPCTests.h @@ -0,0 +1,118 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +#include "FireboltSDK.h" +#include "TestUtils.h" + +typedef enum { + Test1, + Test2, + Test3, + Test4 +} TestEnum; + +namespace FireboltSDK { + typedef int32_t (*Func)(); + + class Tests { + public: + class EventControl { + public: + EventControl() + : _event(false, true) + , _name("EventControl") + { + } + EventControl(string name) + : _event(false, true) + , _name(name) + { + } + ~EventControl() = default; + + public: + void NotifyEvent() + { + _event.SetEvent(); + } + uint32_t WaitForEvent(uint32_t waitTime) + { + return _event.Lock(waitTime); + } + void ResetEvent() + { + _event.ResetEvent(); + } + string Name() const + { + return _name; + } + private: + WPEFramework::Core::Event _event; + string _name; + }; + + private: + typedef std::unordered_map TestFunctionMap; + + public: + Tests(); + virtual ~Tests() = default; + + inline TestFunctionMap& TestList() + { + return _functionMap; + } + + template + static int32_t Main() + { + TESTS fireboltTest; + for (auto i = fireboltTest.TestList().begin(); i != fireboltTest.TestList().end(); i++) { + EXECUTE(i->first.c_str(), i->second); + } + + printf("TOTAL: %i tests; %i PASSED, %i FAILED\n", TotalTests, TotalTestsPassed, (TotalTests - TotalTestsPassed)); + + return 0; + } + + static int32_t GetDeviceId(); + static int32_t GetDeviceVersion(); + static int32_t GetUnKnownMethod(); + + static int32_t SetLifeCycleClose(); + static int32_t SetUnKnownMethod(); + + static int32_t SubscribeEvent(); + static int32_t SubscribeEventwithSameCallback(); + static int32_t SubscribeEventWithMultipleCallback(); + + template + static int32_t SubscribeEventForC(const string& eventName, JsonObject& jsonParameters, CALLBACK& callbackFunc, void* usercb, const void* userdata); + + protected: + static void PrintJsonObject(const JsonObject::Iterator& iterator); + + protected: + std::list menu; + TestFunctionMap _functionMap; + }; +} diff --git a/languages/c/src/shared/test/TestUtils.h b/languages/c/src/shared/test/TestUtils.h new file mode 100644 index 00000000..c54db9f0 --- /dev/null +++ b/languages/c/src/shared/test/TestUtils.h @@ -0,0 +1,38 @@ +#ifndef TEST_UTILS_H +#define TEST_UTILS_H + +#include +#include +#include + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +#define _RESULT(expr, exprorig, result) if (expr) { printf("TestStatus: SUCCESS: %s\n", #exprorig); __pass++; } else printf("TestStatus: FAILED: %s, actual: %lu\n", #exprorig, result) +#define _EVAL(result, expected, op) do { __cnt++; long resval = ((long)(result)); long expval = ((long)(expected)); _RESULT(resval op expval, result op expected, resval); } while(0) +#define _HEAD(name) printf("\n======== %s\n", name); __cnt = 0; __pass = 0 +#define _FOOT(name) printf("\n======== %s - %i PASSED, %i FAILED\n", name, __pass, (__cnt - __pass)); TotalTests += __cnt; TotalTestsPassed += __pass; + +#define EXECUTE(name, test) do { _HEAD(name); test(); _FOOT(name); printf("\n"); } while(0) +#define EXPECT_EQ(result, expected) _EVAL(result, expected, ==) +#define EXPECT_NE(result, expected) _EVAL(result, expected, !=) +#define EXPECT_LT(result, expected) _EVAL(result, expected, <) +#define EXPECT_LE(result, expected) _EVAL(result, expected, <=) +#define EXPECT_GT(result, expected) _EVAL(result, expected, >) +#define EXPECT_GE(result, expected) _EVAL(result, expected, >=) + +#ifdef __cplusplus +extern "C" { +#endif + +extern int __cnt; +extern int __pass; + +extern int TotalTests ; +extern int TotalTestsPassed; + +#ifdef __cplusplus +} +#endif + +#endif // TEST_UTILS_H diff --git a/languages/c/src/types/ImplHelpers.mjs b/languages/c/src/types/ImplHelpers.mjs new file mode 100644 index 00000000..0ac77dd6 --- /dev/null +++ b/languages/c/src/types/ImplHelpers.mjs @@ -0,0 +1,510 @@ +import { capitalize, getFireboltStringType } from "./NativeHelpers.mjs" + +const Indent = '\t' + +const getSdkNameSpace = () => 'FireboltSDK' +const wpeJsonNameSpace = () => 'WPEFramework::Core::JSON' +const camelcase = str => str[0].toLowerCase() + str.substr(1) + +const getObjectManagementImpl = (varName, jsonDataName) => { + + let result = `${varName}_t ${varName}_Acquire(void) +{ + WPEFramework::Core::ProxyType<${jsonDataName}>* type = new WPEFramework::Core::ProxyType<${jsonDataName}>(); + *type = WPEFramework::Core::ProxyType<${jsonDataName}>::Create(); + return (reinterpret_cast<${varName}_t>(type)); +} +void ${varName}_Addref(${varName}_t handle) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${jsonDataName}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); + var->AddRef(); +} +void ${varName}_Release(${varName}_t handle) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${jsonDataName}>* var = reinterpret_cast*>(handle); + var->Release(); + if (var->IsValid() != true) { + delete var; + } +} +bool ${varName}_IsValid(${varName}_t handle) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${jsonDataName}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); + return var->IsValid(); +} +` + return result +} + +const getPropertyAccessorsImpl = (objName, modulePropertyType, subPropertyType, subPropertyName, accessorPropertyType, json = {}, options = {readonly:false, optional:false}) => { + let result = '' + result += `${accessorPropertyType} ${objName}_Get_${subPropertyName}(${objName}_t handle) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${modulePropertyType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); +` + '\n' + if (((json.type === 'object') || (json.type === 'array')) && (accessorPropertyType !== 'char*')) { + result += ` WPEFramework::Core::ProxyType<${subPropertyType}>* element = new WPEFramework::Core::ProxyType<${subPropertyType}>(); + *element = WPEFramework::Core::ProxyType<${subPropertyType}>::Create(); + *(*element) = (*var)->${subPropertyName}; + return (reinterpret_cast<${accessorPropertyType}>(element));` + '\n' + } + else { + if ((typeof json.const === 'string') || (json.type === 'string' && !json.enum) || (accessorPropertyType === 'char*')) { + result += ` return (const_cast<${accessorPropertyType}>((*var)->${subPropertyName}.Value().c_str()));` + '\n' + } + else { + result += ` return (static_cast<${accessorPropertyType}>((*var)->${subPropertyName}.Value()));` + '\n' + } + } + result += `}` + '\n' + + if (!options.readonly) { + let type = (accessorPropertyType === getFireboltStringType()) ? 'char*' : accessorPropertyType + result += `void ${objName}_Set_${subPropertyName}(${objName}_t handle, ${type} value)\n{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${modulePropertyType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); +` + '\n' + + if (((json.type === 'object') || (json.type === 'array')) && (accessorPropertyType !== 'char*')) { + result += ` WPEFramework::Core::ProxyType<${subPropertyType}>* object = reinterpret_cast*>(value); + (*var)->${subPropertyName} = *(*object);` + '\n' + } + else { + result += ` (*var)->${subPropertyName} = value;` + '\n' + } + result += `}` + '\n' + } + + if (options.optional === true) { + result += `bool ${objName}_Has_${subPropertyName}(${objName}_t handle)\n{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${modulePropertyType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); + + return ((*var)->${subPropertyName}.IsSet()); +}` + '\n' + result += `void ${objName}_Clear_${subPropertyName}(${objName}_t handle)\n{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${modulePropertyType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); + ((*var)->${subPropertyName}.Clear()); +}` + '\n' + } + + return result +} +const getArrayAccessorsImpl = (objName, modulePropertyType, objType, subPropertyType, subPropertyName, accessorPropertyType, json = {}) => { + + let propertyName + if (subPropertyName) { + propertyName = '(*var)->' + `${subPropertyName}` + objName = objName + '_' + subPropertyName + } + else { + propertyName = '(*(*var))' + } + + let result = `uint32_t ${objName}Array_Size(${objType} handle) { + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${modulePropertyType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); + + return (${propertyName}.Length()); +}` + '\n' + + result += `${accessorPropertyType} ${objName}Array_Get(${objType} handle, uint32_t index) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${modulePropertyType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid());` + '\n' + + if ((json.type === 'object') || (json.type === 'array')) { + result += `WPEFramework::Core::ProxyType<${subPropertyType}>* object = new WPEFramework::Core::ProxyType<${subPropertyType}>(); + *object = WPEFramework::Core::ProxyType<${subPropertyType}>::Create(); + *(*object) = ${propertyName}.Get(index); + + return (reinterpret_cast<${accessorPropertyType}>(object));` + '\n' + } + else { + if ((typeof json.const === 'string') || (json.type === 'string' && !json.enum)) { + result += ` return (const_cast<${accessorPropertyType}>(${propertyName}.Get(index).Value().c_str()));` + '\n' + } + else { + result += ` return (static_cast<${accessorPropertyType}>(${propertyName}.Get(index)));` + '\n' + } + } + result += `}` + '\n' + + let type = (accessorPropertyType === getFireboltStringType()) ? 'char*' : accessorPropertyType + result += `void ${objName}Array_Add(${objType} handle, ${type} value) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${modulePropertyType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid());` + '\n' + + if ((json.type === 'object') || (json.type === 'array')) { + result += ` ${subPropertyType} element; + element = *(*(reinterpret_cast*>(value)));` + '\n' + } + else { + result += ` ${subPropertyType} element;` + '\n' + result += ` element = value;` + '\n' + } + result += ` + ${propertyName}.Add() = element; +}` + '\n' + + result += `void ${objName}Array_Clear(${objType} handle) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${modulePropertyType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); + + ${propertyName}.Clear(); +}` + '\n' + + return result +} + +const getMapAccessorsImpl = (objName, containerType, subPropertyType, accessorPropertyType, json = {}, options = {readonly:false, optional:false}) => { + let result = `uint32_t ${objName}_KeysCount(${objName}_t handle) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${containerType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); + ${containerType}::Iterator elements = (*var)->Variants(); + uint32_t count = 0; + while (elements.Next()) { + count++; + } + return (count); +}` + '\n' + result += `void ${objName}_AddKey(${objName}_t handle, char* key, ${accessorPropertyType} value) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${containerType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); +` + '\n' + let elementContainer = subPropertyType + if (containerType.includes('VariantContainer')) { + elementContainer = 'WPEFramework::Core::JSON::Variant' + } + if ((json.type === 'object') || (json.type === 'array' && json.items)) { + if (containerType.includes('VariantContainer')) { + result += ` ${subPropertyType}& container = *(*(reinterpret_cast*>(value)));` + '\n' + result += ` string containerStr;` + '\n' + result += ` element.ToString(containerStr);` + '\n' + result += ` WPEFramework::Core::JSON::VariantContainer containerVariant(containerStr);` + '\n' + result += ` WPEFramework::Core::JSON::Variant element = containerVariant;` + '\n' + } + else { + result += ` ${subPropertyType}& element = *(*(reinterpret_cast*>(value)));` + '\n' + } + } else { + result += ` ${elementContainer} element(value);` + '\n' + } + result += ` (*var)->Set(const_cast(key), element); +}` + '\n' + + result += `void ${objName}_RemoveKey(${objName}_t handle, char* key) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${containerType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid()); + + (*var)->Remove(key); +}` + '\n' + + result += `${accessorPropertyType} ${objName}_FindKey(${objName}_t handle, char* key) +{ + ASSERT(handle != NULL); + WPEFramework::Core::ProxyType<${containerType}>* var = reinterpret_cast*>(handle); + ASSERT(var->IsValid());` + '\n' + if ((json.type === 'object') || (json.type === 'array') || + ((json.type === 'string' || (typeof json.const === 'string')) && !json.enum)) { + result += ` ${accessorPropertyType} status = nullptr;` + '\n' + } + else if (json.type === 'boolean') { + result += ` ${accessorPropertyType} status = false;` + '\n' + } + else { + result += ` ${accessorPropertyType} status = 0;` + '\n' + } + + result += ` + if ((*var)->HasLabel(key) == true) {` + if (json.type === 'object') { + result += ` + string objectStr; + (*var)->Get(key).Object().ToString(objectStr); + ${subPropertyType} objectMap; + objectMap.FromString(objectStr); + + WPEFramework::Core::ProxyType<${subPropertyType}>* element = new WPEFramework::Core::ProxyType<${subPropertyType}>(); + *element = WPEFramework::Core::ProxyType<${subPropertyType}>::Create(); + *(*element) = objectMap; + + status = (reinterpret_cast<${accessorPropertyType}>(element));` + '\n' + } + else if (json.type === 'array' && json.items) { + result += ` + WPEFramework::Core::ProxyType<${subPropertyType}>* element = new WPEFramework::Core::ProxyType<${subPropertyType}>(); + *element = WPEFramework::Core::ProxyType<${subPropertyType}>::Create(); + *(*element) = (*var)->Get(key).Array(); + status = (reinterpret_cast<${accessorPropertyType}>(element));` + '\n' + } + else { + if (json.type === 'string' || (typeof json.const === 'string')) { + if (json.enum) { + result += ` + status = (const_cast<${accessorPropertyType}>((*var)->Get(key).));` + '\n' + } + else { + result += ` + status = (const_cast<${accessorPropertyType}>((*var)->Get(key).String().c_str()));` + '\n' + } + } + else if (json.type === 'boolean') { + result += ` + status = (static_cast<${accessorPropertyType}>((*var)->Get(key).Boolean()));` + '\n' + } + else if (json.type === 'number') { + result += ` + status = (static_cast<${accessorPropertyType}>((*var)->Get(key).Float()));` + '\n' + } + else if (json.type === 'integer') { + result += ` + status = (static_cast<${accessorPropertyType}>((*var)->Get(key).Number()));` + '\n' + } + } + result += ` } + return status; +}` + + return result +} + +/* +paramList = [{name='', nativeType='', jsonType='', required=boolean}] +*/ +function getParameterInstantiation(paramList, container = '') { + + let impl = ` ${container.length>0 ? container : 'JsonObject'} jsonParameters;\n` + paramList.forEach(param => { + impl += `\n` + const jsonType = param.jsonType + const name = param.name + if (jsonType.length) { + if (param.required) { + if (param.nativeType.includes('Firebolt_String_t')) { + impl += ` WPEFramework::Core::JSON::Variant ${capitalize(name)} = *(reinterpret_cast<${jsonType}*>(${name}));\n` + } + else if (param.nativeType.includes('_t')) { + impl += ` ${jsonType}& ${capitalize(name)}Container = *(*(reinterpret_cast*>(${camelcase(name)})));\n` + impl += ` string ${capitalize(name)}Str;\n` + impl += ` ${capitalize(name)}Container.ToString(${capitalize(name)}Str);\n` + impl += ` WPEFramework::Core::JSON::VariantContainer ${capitalize(name)}VariantContainer(${capitalize(name)}Str);\n` + impl += ` WPEFramework::Core::JSON::Variant ${capitalize(name)} = ${capitalize(name)}VariantContainer;\n` + } + else { + impl += ` WPEFramework::Core::JSON::Variant ${capitalize(name)} = ${name};\n` + } + impl += ` jsonParameters.Set(_T("${name}"), ${capitalize(name)});` + } + else { + + impl += ` if (${name} != nullptr) {\n` + if (param.nativeType.includes('char*')) { + impl += ` WPEFramework::Core::JSON::Variant ${capitalize(name)} = ${name};\n` + } + else if (param.nativeType.includes('_t')) { + impl += ` ${jsonType}& ${capitalize(name)}Container = *(*(reinterpret_cast*>(${camelcase(name)})));\n` + impl += ` string ${capitalize(name)}Str;\n` + impl += ` ${capitalize(name)}Container.ToString(${capitalize(name)}Str);\n` + impl += ` WPEFramework::Core::JSON::VariantContainer ${capitalize(name)}VariantContainer(${capitalize(name)}Str);\n` + impl += ` WPEFramework::Core::JSON::Variant ${capitalize(name)} = ${capitalize(name)}VariantContainer;\n` + } else { + + impl += ` WPEFramework::Core::JSON::Variant ${capitalize(name)} = *(${name});\n` + } + impl += ` jsonParameters.Set(_T("${name}"), ${capitalize(name)});\n` + impl += ` }` + } + impl += '\n' + } + }) + + return impl +} + +const isNativeType = (type) => (type === 'float' || type === 'char*' || type === 'int32_t' || type === 'bool') + +function getCallbackParametersInstantiation(paramList, container = '') { + + let impl = '' + + if (paramList.length > 0) { + paramList.forEach(param => { + if (param.required !== undefined) { + if (param.nativeType !== 'char*') { + impl += ` ${param.nativeType} ${param.name};\n` + if (param.required === false) { + impl += ` ${param.nativeType}* ${param.name}Ptr = nullptr;\n` + } + } + else { + impl += ` ${getFireboltStringType()} ${param.name};\n` + } + } + }) + impl += `\n WPEFramework::Core::ProxyType<${container}>* jsonResponse;\n` + impl += ` WPEFramework::Core::ProxyType& var = *(reinterpret_cast*>(response)); + + ASSERT(var.IsValid() == true); + if (var.IsValid() == true) { + WPEFramework::Core::JSON::VariantContainer::Iterator elements = var->Variants(); + + while (elements.Next()) { + if (strcmp(elements.Label(), "value") == 0) { + + jsonResponse = new WPEFramework::Core::ProxyType<${container}>(); + string objectStr; + elements.Current().Object().ToString(objectStr); + (*jsonResponse)->FromString(objectStr); + } else if (strcmp(elements.Label(), "context") == 0) { + + WPEFramework::Core::JSON::VariantContainer::Iterator params = elements.Current().Object().Variants(); + while (params.Next()) {\n` + let contextParams = '' + + paramList.forEach(param => { + if (param.required !== undefined) { + if (isNativeType(param.nativeType) === true) { + if (contextParams.length > 0) { + contextParams += ` else if (strcmp(elements.Label(), "${param.name}") == 0) {\n` + } + else { + contextParams += ` if (strcmp(elements.Label(), "${param.name}") == 0) {\n` + } + if (param.nativeType === 'char*') { + contextParams += ` ${getSdkNameSpace()}::JSON::String* ${param.name}Value = new ${getSdkNameSpace()}::JSON::String(); + *${param.name}Value = elements.Current().Value().c_str(); + ${param.name} = reinterpret_cast<${getFireboltStringType()}>(${param.name}Value);\n` + } + else if (param.nativeType === 'bool') { + contextParams += ` ${param.name} = elements.Current().Boolean();\n` + } + else if ((param.nativeType === 'float') || (param.nativeType === 'int32_t')) { + contextParams += ` ${param.name} = elements.Current().Number();\n` + } + if ((param.nativeType !== 'char*') && (param.required === false)) { + contextParams += ` ${param.name}Ptr = &${param.name};\n` + } + contextParams += ` }\n` + } + } + }) + impl += contextParams + impl += ` } + } else { + ASSERT(false); + } + } + }\n` + } else { + + impl +=` WPEFramework::Core::ProxyType<${container}>* jsonResponse = reinterpret_cast*>(response);\n` + } + + return impl +} + +function getCallbackResultInstantiation(nativeType, container = '') { + let impl = '' + if (nativeType === 'char*' || nativeType === 'Firebolt_String_t') { + impl +=` + ${container}* jsonStrResponse = new ${container}(); + *jsonStrResponse = *(*jsonResponse); + jsonResponse->Release();` + '\n' + } + return impl +} + +function getCallbackResponseInstantiation(paramList, nativeType, container = '') { + let impl = '' + + if (paramList.length > 0) { + paramList.forEach(param => { + if (param.required !== undefined) { + if (param.nativeType === 'char*') { + impl += `reinterpret_cast<${getFireboltStringType()}>(${param.name}), ` + } + else if (param.required === true) { + impl += `${param.name}, ` + } + else if (param.required === false) { + impl += `${param.name}Ptr, ` + } + } + }) + } + + if (nativeType === 'char*' || nativeType === 'Firebolt_String_t') { + impl += `reinterpret_cast<${nativeType}>(jsonStrResponse)` + } + else if (nativeType.includes('_t')) { + impl += `reinterpret_cast<${nativeType}>(jsonResponse)` + } + else { + impl += `static_cast<${nativeType}>((*jsonResponse)->Value())` + } + + return impl +} + +function getResultInstantiation (name, nativeType, container, indentLevel = 2) { + + let impl = '' + + if (nativeType) { + impl += `${' '.repeat(indentLevel)}if (${name} != nullptr) {` + '\n' + if (nativeType === 'char*' || nativeType === 'Firebolt_String_t') { + impl += `${' '.repeat(indentLevel + 1)}${container}* strResult = new ${container}(jsonResult);` + '\n' + impl += `${' '.repeat(indentLevel + 1)}*${name} = reinterpret_cast<${getFireboltStringType()}>(strResult);` + '\n' + } else if (nativeType.includes('_t')) { + impl += `${' '.repeat(indentLevel + 1)}WPEFramework::Core::ProxyType<${container}>* resultPtr = new WPEFramework::Core::ProxyType<${container}>();\n` + impl += `${' '.repeat(indentLevel + 1)}*resultPtr = WPEFramework::Core::ProxyType<${container}>::Create();\n` + impl += `${' '.repeat(indentLevel + 1)}*(*resultPtr) = jsonResult;\n` + impl += `${' '.repeat(indentLevel + 1)}*${name} = reinterpret_cast<${nativeType}>(resultPtr);` + '\n' + } else { + impl += `${' '.repeat(indentLevel + 1)}*${name} = jsonResult.Value();` + '\n' + } + impl += `${' '.repeat(indentLevel)}}` + '\n' + } else if (name === 'success') { + impl += `${' '.repeat(indentLevel)}status = (jsonResult.Value() == true) ? FireboltSDKErrorNone : FireboltSDKErrorNotSupported;` + } + + return impl + +} + + +export { + getArrayAccessorsImpl, + getMapAccessorsImpl, + getObjectManagementImpl, + getPropertyAccessorsImpl, + getParameterInstantiation, + getCallbackParametersInstantiation, + getCallbackResultInstantiation, + getCallbackResponseInstantiation, + getResultInstantiation +} diff --git a/languages/c/src/types/JSONHelpers.mjs b/languages/c/src/types/JSONHelpers.mjs new file mode 100644 index 00000000..1c5e3ea4 --- /dev/null +++ b/languages/c/src/types/JSONHelpers.mjs @@ -0,0 +1,57 @@ +const capitalize = str => str[0].toUpperCase() + str.substr(1) +const getSdkNameSpace = () => 'FireboltSDK' +const getJsonDataPrefix = () => 'JsonData_' +const wpeJsonNameSpace = () => 'WPEFramework::Core::JSON' + +const getJsonDataStructName = (modName, name, prefix = '') => { + let result =((prefix && prefix.length > 0) && (prefix !== name)) ? `${capitalize(modName)}::${getJsonDataPrefix()}${capitalize(prefix)}_${capitalize(name)}` : `${capitalize(modName)}::${getJsonDataPrefix()}${capitalize(name)}` + + return ((result.includes(wpeJsonNameSpace()) === true) ? result : `${getSdkNameSpace()}::${result}`) +} + +function getJsonContainerDefinition (schema, name, props) { + let c = schema.description ? (' /*\n * ${info.title} - ' + `${schema.description}\n */\n`) : '' + name = getJsonDataPrefix() + capitalize(name) + c += ` class ${name}: public WPEFramework::Core::JSON::Container { + public: + ~${name}() override = default; + + public: + ${name}() + : WPEFramework::Core::JSON::Container() + {` + + props.forEach(prop => { + c += `\n Add(_T("${prop.name}"), &${capitalize(prop.name)});` + }) + c += `\n }\n` + c += `\n ${name}(const ${name}& copy) + {` + props.forEach(prop => { + c += `\n Add(_T("${prop.name}"), &${capitalize(prop.name)});` + c += `\n ${capitalize(prop.name)} = copy.${capitalize(prop.name)};` + }) + c += ` + }\n + ${name}& operator=(const ${name}& rhs) + {` + props.forEach(prop => { + c += `\n ${capitalize(prop.name)} = rhs.${capitalize(prop.name)};` + }) + c += `\n return (*this); + }\n + public:` + + props.forEach(prop => { + c += `\n ${prop.type} ${capitalize(prop.name)};` + }) + + c += '\n };' + return c +} + +export { + getJsonContainerDefinition, + getJsonDataStructName, + getJsonDataPrefix +} diff --git a/languages/c/src/types/NativeHelpers.mjs b/languages/c/src/types/NativeHelpers.mjs new file mode 100644 index 00000000..d2540279 --- /dev/null +++ b/languages/c/src/types/NativeHelpers.mjs @@ -0,0 +1,172 @@ +/* + * 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 + */ + +import helpers from 'crocks/helpers/index.js' +const { compose, getPathOr } = helpers +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 deepmerge from 'deepmerge' + +const { isObject, isArray, propEq, pathSatisfies, hasProp, propSatisfies } = predicates + +const getModuleName = json => getPathOr(null, ['info', 'title'], json) || json.title || 'missing' + +const getFireboltStringType = () => 'Firebolt_String_t' + +const capitalize = str => str[0].toUpperCase() + str.substr(1) +const description = (title, str='') => '/* ' + title + (str.length > 0 ? ' - ' + str : '') + ' */' +const isOptional = (prop, json) => (!json.required || !json.required.includes(prop)) + +const SdkTypesPrefix = 'Firebolt' + +const Indent = ' ' + +const getNativeType = (json, fireboltString = false) => { + let type + let jsonType = json.const ? typeof json.const : json.type + if (jsonType === 'string') { + type = 'char*' + if (fireboltString) { + type = getFireboltStringType() + } + } + else if (jsonType === 'number') { + type = 'float' + } + else if (jsonType === 'integer') { + type = 'int32_t' + + } + else if (jsonType === 'boolean') { + type = 'bool' + } + else if (jsonType === 'null' ) { + type = 'void' + } + return type +} + +const getObjectManagement = varName => { + + let result = `typedef struct ${varName}_s* ${varName}_t; +${varName}_t ${varName}_Acquire(void); +void ${varName}_Addref(${varName}_t handle); +void ${varName}_Release(${varName}_t handle); +bool ${varName}_IsValid(${varName}_t handle); +` + return result +} + +const getPropertyAccessors = (objName, propertyName, propertyType, options = {level:0, readonly:false, optional:false}) => { + let result = `${Indent.repeat(options.level)}${propertyType} ${objName}_Get_${propertyName}(${objName}_t handle);` + '\n' + + if (!options.readonly) { + let type = (propertyType === getFireboltStringType()) ? 'char*' : propertyType + result += `${Indent.repeat(options.level)}void ${objName}_Set_${propertyName}(${objName}_t handle, ${type} ${propertyName.toLowerCase()});` + '\n' + } + + if (options.optional === true) { + result += `${Indent.repeat(options.level)}bool ${objName}_Has_${propertyName}(${objName}_t handle);` + '\n' + result += `${Indent.repeat(options.level)}void ${objName}_Clear_${propertyName}(${objName}_t handle);` + '\n' + } + + return result +} + +const getMapAccessors = (typeName, accessorPropertyType, level = 0) => { + + let res + + res = `${Indent.repeat(level)}uint32_t ${typeName}_KeysCount(${typeName}_t handle);` + '\n' + res += `${Indent.repeat(level)}void ${typeName}_AddKey(${typeName}_t handle, char* key, ${accessorPropertyType} value);` + '\n' + res += `${Indent.repeat(level)}void ${typeName}_RemoveKey(${typeName}_t handle, char* key);` + '\n' + res += `${Indent.repeat(level)}${accessorPropertyType} ${typeName}_FindKey(${typeName}_t handle, char* key);` + '\n' + + return res +} + +const getTypeName = (moduleName, varName, prefix = '', upperCase = false, capitalCase = true) => { + + let mName = upperCase ? moduleName.toUpperCase() : capitalize(moduleName) + let vName = upperCase ? varName.toUpperCase() : capitalCase ? capitalize(varName) : varName + if (prefix && prefix.length > 0) { + prefix = (prefix !== varName) ? (upperCase ? prefix.toUpperCase() : capitalize(prefix)) : '' + } + prefix = (prefix && prefix.length > 0) ?(upperCase ? prefix.toUpperCase() : capitalize(prefix)) : prefix + let name = (prefix && prefix.length > 0) ? `${mName}_${prefix}_${vName}` : `${mName}_${vName}` + return name +} + +const getArrayAccessors = (arrayName, propertyType, valueType) => { + + let res = `uint32_t ${arrayName}Array_Size(${propertyType}_t handle);` + '\n' + res += `${valueType} ${arrayName}Array_Get(${propertyType}_t handle, uint32_t index);` + '\n' + res += `void ${arrayName}Array_Add(${propertyType}_t handle, ${valueType} value);` + '\n' + res += `void ${arrayName}Array_Clear(${propertyType}_t handle);` + '\n' + + return res +} + +const enumValue = (val,prefix) => { + const keyName = val.replace(/[\.\-:]/g, '_').replace(/\+/g, '_plus').replace(/([a-z])([A-Z0-9])/g, '$1_$2').toUpperCase() + return ` ${prefix.toUpperCase()}_${keyName.toUpperCase()}` +} + +const generateEnum = (schema, prefix)=> { + if (!schema.enum) { + return '' + } + else { + let str = `typedef enum {\n` + str += schema.enum.map(e => enumValue(e, prefix)).join(',\n') + str += `\n} ${prefix};\n` + return str + } +} + +/* +paramList = [{name='', nativeType='', jsonType='', required=boolean}] +*/ + +const getContextParams = (paramList) => paramList.map(param => param.nativeType + (!param.required ? '*' : '') + ' ' + param.name).join(', ') + +function getPropertyGetterSignature(property, module, propType, paramList = []) { + + let contextParams = '' + contextParams = getContextParams(paramList) + return `int32_t ${capitalize(getModuleName(module))}_Get${capitalize(property.name)}( ${contextParams}${contextParams.length > 0 ? ', ':''}${propType}* ${property.result.name || property.name} )` +} + +export { + getNativeType, + getModuleName, + getPropertyGetterSignature, + getMapAccessors, + getArrayAccessors, + capitalize, + description, + getTypeName, + getObjectManagement, + getPropertyAccessors, + isOptional, + generateEnum, + getFireboltStringType +} diff --git a/languages/c/templates/codeblocks/export.c b/languages/c/templates/codeblocks/export.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/codeblocks/mock-import.c b/languages/c/templates/codeblocks/mock-import.c new file mode 100644 index 00000000..5d22512a --- /dev/null +++ b/languages/c/templates/codeblocks/mock-import.c @@ -0,0 +1 @@ +import { default as _${info.title} } from './${info.title}/defaults.mjs' \ No newline at end of file diff --git a/languages/c/templates/codeblocks/mock-parameter.c b/languages/c/templates/codeblocks/mock-parameter.c new file mode 100644 index 00000000..63e63902 --- /dev/null +++ b/languages/c/templates/codeblocks/mock-parameter.c @@ -0,0 +1 @@ + ${info.title}: _${info.title}, \ No newline at end of file diff --git a/languages/c/templates/codeblocks/module.c b/languages/c/templates/codeblocks/module.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/codeblocks/setter.c b/languages/c/templates/codeblocks/setter.c new file mode 100644 index 00000000..f62c8683 --- /dev/null +++ b/languages/c/templates/codeblocks/setter.c @@ -0,0 +1,7 @@ +/* ${method.rpc.name} - ${method.description} */ +int32_t ${info.Title}_${method.Name}( ${method.signature.params} ) +{ + const string method = _T("${info.title.lowercase}.${method.rpc.name}"); +${if.params}${method.params.serialization}${end.if.params} + return FireboltSDK::Properties::Set(method, jsonParameters); +} diff --git a/languages/c/templates/declarations/clear.c b/languages/c/templates/declarations/clear.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/declarations/default.c b/languages/c/templates/declarations/default.c new file mode 100644 index 00000000..e6b49808 --- /dev/null +++ b/languages/c/templates/declarations/default.c @@ -0,0 +1,4 @@ +/* ${method.name} - ${method.description} +${method.params.annotations}${if.deprecated} * @deprecated ${method.deprecation}${end.if.deprecated} */ +int32_t ${info.Title}_${method.Name}( ${method.signature.params}${if.result}${if.params}, ${end.if.params}OUT ${method.result.type}* ${method.result.name}${end.if.result}${if.signature.empty}void${end.if.signature.empty} ); + diff --git a/languages/c/templates/declarations/event.c b/languages/c/templates/declarations/event.c new file mode 100644 index 00000000..3638be52 --- /dev/null +++ b/languages/c/templates/declarations/event.c @@ -0,0 +1,5 @@ +/* ${method.name} - ${method.description} */ +typedef void (*${info.Title}${method.Name}Callback)( const void* userData, ${event.signature.callback.params}${if.event.params}, ${end.if.event.params}${event.result.type} ); +int32_t ${info.Title}_Register_${method.Name}( ${event.signature.params}${if.event.params}, ${end.if.event.params}${info.Title}${method.Name}Callback userCB, const void* userData ); +int32_t ${info.Title}_Unregister_${method.Name}( ${info.Title}${method.Name}Callback userCB); + diff --git a/languages/c/templates/declarations/listen.c b/languages/c/templates/declarations/listen.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/declarations/once.c b/languages/c/templates/declarations/once.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/declarations/polymorphic-pull-event.c b/languages/c/templates/declarations/polymorphic-pull-event.c new file mode 100644 index 00000000..b9c7f424 --- /dev/null +++ b/languages/c/templates/declarations/polymorphic-pull-event.c @@ -0,0 +1,5 @@ +/* ${method.name} - ${method.description} */ +typedef void* (*${info.Title}${method.Name}Callback)( const void* userData, ${method.pulls.param.type} ); +int32_t ${info.Title}_Register_${method.Name}( ${info.Title}${method.Name}Callback userCB, const void* userData ); +int32_t ${info.Title}_Unregister_${method.Name}( ${info.Title}${method.Name}Callback userCB); + diff --git a/languages/c/templates/declarations/polymorphic-pull.c b/languages/c/templates/declarations/polymorphic-pull.c new file mode 100644 index 00000000..7956f87f --- /dev/null +++ b/languages/c/templates/declarations/polymorphic-pull.c @@ -0,0 +1,3 @@ +/* ${method.name} - ${method.description} */ +int32_t ${info.Title}_Push${method.Name}( ${method.signature.params} ); + diff --git a/languages/c/templates/declarations/polymorphic-reducer.c b/languages/c/templates/declarations/polymorphic-reducer.c new file mode 100644 index 00000000..cf974e1a --- /dev/null +++ b/languages/c/templates/declarations/polymorphic-reducer.c @@ -0,0 +1,7 @@ +/* + * ${method.summary} + * ${method.params} + */ +${method.signature} + +// TODO: generate reducer signature diff --git a/languages/c/templates/declarations/property.c b/languages/c/templates/declarations/property.c new file mode 100644 index 00000000..3550b45e --- /dev/null +++ b/languages/c/templates/declarations/property.c @@ -0,0 +1,5 @@ +/* + * ${method.summary} + * ${method.params} + */ +${method.signature} diff --git a/languages/c/templates/declarations/provide.c b/languages/c/templates/declarations/provide.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/declarations/setter.c b/languages/c/templates/declarations/setter.c new file mode 100644 index 00000000..a40fd4fc --- /dev/null +++ b/languages/c/templates/declarations/setter.c @@ -0,0 +1,2 @@ +/* ${method.rpc.name} - ${method.description} */ +int32_t ${info.Title}_${method.Name}( ${method.signature.params} ); diff --git a/languages/c/templates/defaults/default.c b/languages/c/templates/defaults/default.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/defaults/property.c b/languages/c/templates/defaults/property.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/imports/calls-metrics.cpp b/languages/c/templates/imports/calls-metrics.cpp new file mode 100644 index 00000000..4ba289b3 --- /dev/null +++ b/languages/c/templates/imports/calls-metrics.cpp @@ -0,0 +1 @@ +#include "metrics.h" diff --git a/languages/c/templates/imports/default.cpp b/languages/c/templates/imports/default.cpp new file mode 100644 index 00000000..caf84bc9 --- /dev/null +++ b/languages/c/templates/imports/default.cpp @@ -0,0 +1 @@ +#include "jsondata_${info.title.lowercase}.h" diff --git a/languages/c/templates/imports/default.h b/languages/c/templates/imports/default.h new file mode 100644 index 00000000..56a97a40 --- /dev/null +++ b/languages/c/templates/imports/default.h @@ -0,0 +1 @@ +#include "common/${info.title.lowercase}.h" diff --git a/languages/c/templates/imports/default.jsondata b/languages/c/templates/imports/default.jsondata new file mode 100644 index 00000000..caf84bc9 --- /dev/null +++ b/languages/c/templates/imports/default.jsondata @@ -0,0 +1 @@ +#include "jsondata_${info.title.lowercase}.h" diff --git a/languages/c/templates/methods/calls-metrics.c b/languages/c/templates/methods/calls-metrics.c new file mode 100644 index 00000000..3ad9ec0f --- /dev/null +++ b/languages/c/templates/methods/calls-metrics.c @@ -0,0 +1,30 @@ +/* ${method.rpc.name} - ${method.description} */ +void Metrics_${method.Name}Dispatcher(const void*${if.result} result${end.if.result}) { + Metrics_${method.Name}(${if.result}(static_cast<${method.result.json.type}>(const_cast(result)))${end.if.result}); +} +int32_t ${info.Title}_${method.Name}( ${method.signature.params}${if.result}${if.params}, ${end.if.params}OUT ${method.result.type}* ${method.result.name}${end.if.result}${if.signature.empty}void${end.if.signature.empty} ) { + + int32_t status = FireboltSDKErrorUnavailable; + FireboltSDK::Transport* transport = FireboltSDK::Accessor::Instance().GetTransport(); + if (transport != nullptr) { + + ${method.params.serialization.with.indent} + ${method.result.json.type} jsonResult; + status = transport->Invoke("${info.title}.${method.rpc.name}", jsonParameters, jsonResult); + if (status == FireboltSDKErrorNone) { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "${info.Title}.${method.rpc.name} is successfully invoked"); + ${method.result.instantiation.with.indent} + + void* result = nullptr; + ${if.result}result = static_cast(new ${method.result.json.type});${end.if.result} + WPEFramework::Core::ProxyType job = WPEFramework::Core::ProxyType(WPEFramework::Core::ProxyType::Create(Metrics_${method.Name}Dispatcher, result)); + WPEFramework::Core::IWorkerPool::Instance().Submit(job); + } + + } else { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "Error in getting Transport err = %d", status); + } + + return status; +} + diff --git a/languages/c/templates/methods/clear.c b/languages/c/templates/methods/clear.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/methods/default.c b/languages/c/templates/methods/default.c new file mode 100644 index 00000000..e1d9e701 --- /dev/null +++ b/languages/c/templates/methods/default.c @@ -0,0 +1,22 @@ +/* ${method.rpc.name} - ${method.description} */ +int32_t ${info.Title}_${method.Name}( ${method.signature.params}${if.result}${if.params}, ${end.if.params}OUT ${method.result.type}* ${method.result.name}${end.if.result}${if.signature.empty}void${end.if.signature.empty} ) { + + int32_t status = FireboltSDKErrorUnavailable; + FireboltSDK::Transport* transport = FireboltSDK::Accessor::Instance().GetTransport(); + if (transport != nullptr) { + + ${method.params.serialization.with.indent} + ${method.result.json.type} jsonResult; + status = transport->Invoke("${info.title.lowercase}.${method.rpc.name}", jsonParameters, jsonResult); + if (status == FireboltSDKErrorNone) { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "${info.Title}.${method.rpc.name} is successfully invoked"); + ${method.result.instantiation.with.indent} + } + + } else { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "Error in getting Transport err = %d", status); + } + + return status; +} + diff --git a/languages/c/templates/methods/event.c b/languages/c/templates/methods/event.c new file mode 100644 index 00000000..85ae3b48 --- /dev/null +++ b/languages/c/templates/methods/event.c @@ -0,0 +1,27 @@ +/* ${method.rpc.name} - ${method.description} */ +static void ${info.Title}${method.Name}InnerCallback( void* userCB, const void* userData, void* response ) +{ +${event.callback.params.serialization} + ASSERT(jsonResponse->IsValid() == true); + if (jsonResponse->IsValid() == true) { +${event.callback.result.instantiation} + ${info.Title}${method.Name}Callback callback = reinterpret_cast<${info.Title}${method.Name}Callback>(userCB); + callback(userData, ${event.callback.response.instantiation}); + } +} +int32_t ${info.Title}_Register_${method.Name}( ${event.signature.params}${if.event.params}, ${end.if.event.params}${info.Title}${method.Name}Callback userCB, const void* userData ) +{ + const string eventName = _T("${info.title.lowercase}.${method.rpc.name}"); + int32_t status = FireboltSDKErrorNone; + + if (userCB != nullptr) { + ${event.params.serialization} + status = FireboltSDK::Event::Instance().Subscribe<${event.result.json.type}>(eventName, jsonParameters, ${info.Title}${method.Name}InnerCallback, reinterpret_cast(userCB), userData); + } + return status; +} +int32_t ${info.Title}_Unregister_${method.Name}( ${info.Title}${method.Name}Callback userCB) +{ + return FireboltSDK::Event::Instance().Unsubscribe(_T("${info.title.lowercase}.${method.rpc.name}"), reinterpret_cast(userCB)); +} + diff --git a/languages/c/templates/methods/listen.c b/languages/c/templates/methods/listen.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/methods/once.c b/languages/c/templates/methods/once.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/methods/polymorphic-pull-event.c b/languages/c/templates/methods/polymorphic-pull-event.c new file mode 100644 index 00000000..49aa1dba --- /dev/null +++ b/languages/c/templates/methods/polymorphic-pull-event.c @@ -0,0 +1,54 @@ +/* ${method.rpc.name} - ${method.description} */ +static void ${info.Title}${method.Name}InnerCallback( void* userCB, const void* userData, void* response ) +{ +${event.callback.params.serialization} + ASSERT(jsonResponse->IsValid() == true); + if (jsonResponse->IsValid() == true) { + + ${info.Title}${method.Name}Callback callback = reinterpret_cast<${info.Title}${method.Name}Callback>(userCB); + + WPEFramework::Core::ProxyType<${method.pulls.param.json.type}>* requestParam = new WPEFramework::Core::ProxyType<${method.pulls.param.json.type}>(); + *requestParam = WPEFramework::Core::ProxyType<${method.pulls.param.json.type}>::Create(); + *(*requestParam) = (*jsonResponse)->${event.pulls.param.name}Parameters; + + ${method.pulls.type} result = reinterpret_cast<${method.pulls.type}>(callback(userData, reinterpret_cast<${method.pulls.param.type}>(requestParam))); + + JsonObject jsonParameters; + WPEFramework::Core::JSON::Variant CorrelationId = (*jsonResponse)->CorrelationId.Value(); + jsonParameters.Set(_T("correlationId"), CorrelationId); + + ${method.pulls.json.type}& resultObj = *(*(reinterpret_cast*>(result))); + string resultStr; + resultObj.ToString(resultStr); + WPEFramework::Core::JSON::VariantContainer resultContainer(resultStr); + WPEFramework::Core::JSON::Variant Result = resultContainer; + jsonParameters.Set(_T("result"), Result); + + FireboltSDK::Transport* transport = FireboltSDK::Accessor::Instance().GetTransport(); + if (transport != nullptr) { + WPEFramework::Core::JSON::Boolean jsonResult; + int32_t status = transport->Invoke(_T("${info.title.lowercase}.${method.pulls.for}"), jsonParameters, jsonResult); + if (status == FireboltSDKErrorNone) { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "${info.Title}.${method.rpc.name} is successfully pushed with status as %d", jsonResult.Value()); + } + } else { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "Error in getting Transport"); + } + } +} +int32_t ${info.Title}_Register_${method.Name}( ${info.Title}${method.Name}Callback userCB, const void* userData ) +{ + const string eventName = _T("${info.title.lowercase}.${method.rpc.name}"); + int32_t status = FireboltSDKErrorNone; + + if (userCB != nullptr) { + ${event.params.serialization} + status = FireboltSDK::Event::Instance().Subscribe<${event.result.json.type}>(eventName, jsonParameters, ${info.Title}${method.Name}InnerCallback, reinterpret_cast(userCB), userData); + } + return status; +} +int32_t ${info.Title}_Unregister_${method.Name}( ${info.Title}${method.Name}Callback userCB) +{ + return FireboltSDK::Event::Instance().Unsubscribe(_T("${info.title.lowercase}.${method.rpc.name}"), reinterpret_cast(userCB)); +} + diff --git a/languages/c/templates/methods/polymorphic-pull.c b/languages/c/templates/methods/polymorphic-pull.c new file mode 100644 index 00000000..d88b49aa --- /dev/null +++ b/languages/c/templates/methods/polymorphic-pull.c @@ -0,0 +1,23 @@ +/* ${method.rpc.name} - ${method.description} */ +int32_t ${info.Title}_Push${method.Name}( ${method.signature.params} ) +{ + int32_t status = FireboltSDKErrorUnavailable; + + FireboltSDK::Transport* transport = FireboltSDK::Accessor::Instance().GetTransport(); + if (transport != nullptr) { + string correlationId = ""; + ${method.params.serialization.with.indent} + + WPEFramework::Core::JSON::Boolean jsonResult; + status = transport->Invoke(_T("${info.title.lowercase}.${method.rpc.name}"), jsonParameters, jsonResult); + if (status == FireboltSDKErrorNone) { + FIREBOLT_LOG_INFO(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "${info.Title}.${method.rpc.name} is successfully pushed with status as %d", jsonResult.Value()); + status = (jsonResult.Value() == true) ? FireboltSDKErrorNone : FireboltSDKErrorNotSupported; + } + } else { + FIREBOLT_LOG_ERROR(FireboltSDK::Logger::Category::OpenRPC, FireboltSDK::Logger::Module(), "Error in getting Transport"); + } + + return status; +} + diff --git a/languages/c/templates/methods/polymorphic-reducer.c b/languages/c/templates/methods/polymorphic-reducer.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/methods/property.c b/languages/c/templates/methods/property.c new file mode 100644 index 00000000..8bf6a516 --- /dev/null +++ b/languages/c/templates/methods/property.c @@ -0,0 +1,14 @@ +/* ${method.rpc.name} - ${method.description} */ +int32_t ${info.Title}_Get${method.Name}( ${method.signature.params}${if.params}, ${end.if.params}OUT ${method.result.type}* ${method.result.name} ) +{ + const string method = _T("${info.title.lowercase}.${method.rpc.name}"); +${if.params}${method.params.serialization}${end.if.params} + ${method.result.json} jsonResult; + ${if.params}int32_t status = FireboltSDK::Properties::Get(method, jsonParameters, jsonResult);${end.if.params} + ${if.params.empty}int32_t status = FireboltSDK::Properties::Get(method, jsonResult);${end.if.params.empty} + if (status == FireboltSDKErrorNone) { +${method.result.instantiation} + } + return status; +} +${method.setter} diff --git a/languages/c/templates/methods/provide.c b/languages/c/templates/methods/provide.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/methods/setter.c b/languages/c/templates/methods/setter.c new file mode 100644 index 00000000..e69de29b diff --git a/languages/c/templates/modules/include/module.h b/languages/c/templates/modules/include/module.h new file mode 100644 index 00000000..1c8d427b --- /dev/null +++ b/languages/c/templates/modules/include/module.h @@ -0,0 +1,44 @@ +/* + * Copyright 2023 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 + */ + +#ifndef ${info.TITLE}_H +#define ${info.TITLE}_H + +#include +/* ${IMPORTS} */ + +${if.declarations}#ifdef __cplusplus +extern "C" { +#endif + +// Enums +/* ${ENUMS} */ + +// Accessors +/* ${ACCESSORS} */ + + + +// Methods & Events +/* ${DECLARATIONS} */ + +#ifdef __cplusplus +} +#endif${end.if.declarations} + +#endif // Header Include Guard diff --git a/languages/c/templates/modules/src/module.cpp b/languages/c/templates/modules/src/module.cpp new file mode 100644 index 00000000..19727924 --- /dev/null +++ b/languages/c/templates/modules/src/module.cpp @@ -0,0 +1,49 @@ +/* + * Copyright 2023 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 + */ + +#include "FireboltSDK.h" +/* ${IMPORTS} */ +#include "${info.title.lowercase}.h" + +${if.types}namespace FireboltSDK { + namespace ${info.Title} { + // Types + /* ${TYPES} */ + } +}${end.if.types} + +/* ${ENUMS} */ + +${if.definitions}#ifdef __cplusplus +extern "C" { +#endif + +// Accessors +/* ${ACCESSORS} */ + + + +// Methods +/* ${METHODS} */ + +// Events +/* ${EVENTS} */ + +#ifdef __cplusplus +} +#endif${end.if.definitions} diff --git a/languages/c/templates/parameters/default.c b/languages/c/templates/parameters/default.c new file mode 100644 index 00000000..e6e3b8ba --- /dev/null +++ b/languages/c/templates/parameters/default.c @@ -0,0 +1 @@ +${method.param.type} ${method.param.name} \ No newline at end of file diff --git a/languages/c/templates/parameters/json.c b/languages/c/templates/parameters/json.c new file mode 100644 index 00000000..5ee36bec --- /dev/null +++ b/languages/c/templates/parameters/json.c @@ -0,0 +1,3 @@ + ${json.param.type} ${method.param.Name} = ${method.param.name}; + jsonParameters.Add("_T(${method.param.name})", &${method.param.Name}); + diff --git a/languages/c/templates/schemas/default.c b/languages/c/templates/schemas/default.c new file mode 100644 index 00000000..9a52cff7 --- /dev/null +++ b/languages/c/templates/schemas/default.c @@ -0,0 +1 @@ +${schema.shape} diff --git a/languages/c/templates/schemas/include/common/module.h b/languages/c/templates/schemas/include/common/module.h new file mode 100644 index 00000000..90f1f031 --- /dev/null +++ b/languages/c/templates/schemas/include/common/module.h @@ -0,0 +1,39 @@ +/* + * Copyright 2023 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 + */ + +#ifndef COMMON_${info.TITLE}_H +#define COMMON_${info.TITLE}_H + +#include +/* ${IMPORTS} */ + +${if.declarations}#ifdef __cplusplus +extern "C" { +#endif + +// Enums +/* ${ENUMS} */ + +// Types +/* ${TYPES} */ + +#ifdef __cplusplus +} +#endif${end.if.declarations} + +#endif // Header Include Guard diff --git a/languages/c/templates/schemas/src/jsondata_module.h b/languages/c/templates/schemas/src/jsondata_module.h new file mode 100644 index 00000000..81084440 --- /dev/null +++ b/languages/c/templates/schemas/src/jsondata_module.h @@ -0,0 +1,30 @@ +/* + * Copyright 2023 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 + */ + +#pragma once + +/* ${IMPORTS} */ +#include "common/${info.title.lowercase}.h" + +${if.schemas}namespace FireboltSDK { + namespace ${info.Title} { + // Types + + /* ${SCHEMAS} */ + } +}${end.if.schemas} diff --git a/languages/c/templates/schemas/src/module_common.cpp b/languages/c/templates/schemas/src/module_common.cpp new file mode 100644 index 00000000..5bfa7719 --- /dev/null +++ b/languages/c/templates/schemas/src/module_common.cpp @@ -0,0 +1,42 @@ +/* + * Copyright 2023 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 + */ + +#include "FireboltSDK.h" +/* ${IMPORTS} */ +#include "jsondata_${info.title.lowercase}.h" + +/* ${ENUMS} */ + +${if.definitions}#ifdef __cplusplus +extern "C" { +#endif + +// Accessors +/* ${ACCESSORS} */ + + + +// Methods +/* ${METHODS} */ + +// Events +/* ${EVENTS} */ + +#ifdef __cplusplus +} +#endif${end.if.definitions} diff --git a/languages/c/templates/sdk/scripts/build.sh b/languages/c/templates/sdk/scripts/build.sh new file mode 100755 index 00000000..149cd1ae --- /dev/null +++ b/languages/c/templates/sdk/scripts/build.sh @@ -0,0 +1,40 @@ +#!/bin/bash +usage() +{ + echo "options:" + echo " -p sdk path" + echo " -s sysroot path" + echo " -c clear build" + echo " -l enable static build" + echo " -t enable test" + echo " -h : help" + echo + echo "usage: " + echo " ./build.sh -p path -tc" +} + +SdkPath="." +EnableTest="OFF" +SysrootPath=${SYSROOT_PATH} +ClearBuild="N" +EnableStaticLib="OFF" +while getopts p:s:clth flag +do + case "${flag}" in + p) SdkPath="${OPTARG}";; + s) SysrootPath="${OPTARG}";; + c) ClearBuild="Y";; + l) EnableStaticLib="ON";; + t) EnableTest="ON";; + h) usage && exit 1;; + esac +done + +if [ "${ClearBuild}" == "Y" ]; +then + rm -rf ${SdkPath}/build +fi + +cmake -B${SdkPath}/build -S${SdkPath} -DSYSROOT_PATH=${SysrootPath} -DENABLE_TESTS=${EnableTest} -DHIDE_NON_EXTERNAL_SYMBOLS=OFF -DFIREBOLT_ENABLE_STATIC_LIB=${EnableStaticLib} +cmake --build ${SdkPath}/build +cmake --install ${SdkPath}/build --prefix=${SdkPath}/build/Firebolt/usr diff --git a/languages/c/templates/sdk/scripts/install.sh b/languages/c/templates/sdk/scripts/install.sh new file mode 100755 index 00000000..24bd1c4c --- /dev/null +++ b/languages/c/templates/sdk/scripts/install.sh @@ -0,0 +1,58 @@ +#!/bin/bash +usage() +{ + echo "options:" + echo " -i install path" + echo " -s sdk path" + echo " -m module name. i.e, core/manage" + echo + echo "usage: " + echo " ./install.sh -i path -s sdk path -m core" +} + +SdkPath=".." +InstallPath=".." +ModuleName="core" +while getopts i:s:m:h flag +do + case "${flag}" in + i) InstallPath="${OPTARG}";; + s) SdkPath="${OPTARG}";; + m) ModuleName="${OPTARG}";; + h) usage && exit 1;; + esac +done + +GetVersion() +{ + PackagePath=${SdkPath}/../../../../../../package-lock.json + InputKey="name\": \"@firebolt-js/openrpc" + LineNo="$(grep -n "${InputKey}" ${PackagePath} | head -n 1 | cut -d: -f1)" + VersionLine=$((LineNo++)) + eval "array=(`sed -n "${LineNo}p" < ${PackagePath} | sed 's/\"/\n/g'`)" + Version=${array[2]} +} + +Version=0.0 +GetVersion +ReleaseName=firebolt-${ModuleName}-native-sdk-${Version} +ReleasePath=${InstallPath}/${ReleaseName} + +rm -rf ${ReleasePath} +mkdir -p ${ReleasePath} +cp -ar ${SdkPath}/src ${ReleasePath} +cp -ar ${SdkPath}/include ${ReleasePath} +cp -ar ${SdkPath}/cmake ${ReleasePath} +cp -ar ${SdkPath}/scripts/build.sh ${ReleasePath} +cp -ar ${SdkPath}/CMakeLists.txt ${ReleasePath} +cp -ar ${SdkPath}/ctest ${ReleasePath}/test + +sed -i '/EnableTest="ON";;/d' ${ReleasePath}/build.sh +sed -i 's/getopts p:s:tch/getopts p:s:ch/g' ${ReleasePath}/build.sh +sed -i '/enable test/d' ${ReleasePath}/build.sh +sed -i '/EnableTest="OFF"/d' ${ReleasePath}/build.sh +sed -i 's/ -DENABLE_TESTS=${EnableTest}//g' ${ReleasePath}/build.sh + +cd ${ReleasePath}/../ +tar -cvzf ${ReleaseName}.tgz ${ReleaseName}/* +cd - diff --git a/languages/c/templates/sections/accessors.c b/languages/c/templates/sections/accessors.c new file mode 100644 index 00000000..9295133c --- /dev/null +++ b/languages/c/templates/sections/accessors.c @@ -0,0 +1 @@ +${schema.list} diff --git a/languages/c/templates/sections/declarations.c b/languages/c/templates/sections/declarations.c new file mode 100644 index 00000000..b3ef974d --- /dev/null +++ b/languages/c/templates/sections/declarations.c @@ -0,0 +1 @@ +${declaration.list} diff --git a/languages/c/templates/sections/enum.cpp b/languages/c/templates/sections/enum.cpp new file mode 100644 index 00000000..e5165c61 --- /dev/null +++ b/languages/c/templates/sections/enum.cpp @@ -0,0 +1,5 @@ +namespace WPEFramework { + +${schema.list} + +} diff --git a/languages/c/templates/sections/events.c b/languages/c/templates/sections/events.c new file mode 100644 index 00000000..5be10409 --- /dev/null +++ b/languages/c/templates/sections/events.c @@ -0,0 +1 @@ +${event.list} diff --git a/languages/c/templates/sections/methods.c b/languages/c/templates/sections/methods.c new file mode 100644 index 00000000..e8200eb0 --- /dev/null +++ b/languages/c/templates/sections/methods.c @@ -0,0 +1 @@ +${method.list} diff --git a/languages/c/templates/sections/methods_accessors.c b/languages/c/templates/sections/methods_accessors.c new file mode 100644 index 00000000..9295133c --- /dev/null +++ b/languages/c/templates/sections/methods_accessors.c @@ -0,0 +1 @@ +${schema.list} diff --git a/languages/c/templates/sections/methods_types.c b/languages/c/templates/sections/methods_types.c new file mode 100644 index 00000000..9295133c --- /dev/null +++ b/languages/c/templates/sections/methods_types.c @@ -0,0 +1 @@ +${schema.list} diff --git a/languages/c/templates/sections/provider-interfaces.c b/languages/c/templates/sections/provider-interfaces.c new file mode 100644 index 00000000..418d7abf --- /dev/null +++ b/languages/c/templates/sections/provider-interfaces.c @@ -0,0 +1,11 @@ + // Provider Interfaces + + interface ProviderSession { + correlationId(): string // Returns the correlation id of the current provider session + } + + interface FocusableProviderSession extends ProviderSession { + focus(): Promise // Requests that the provider app be moved into focus to prevent a user experience + } + + ${providers.list} \ No newline at end of file diff --git a/languages/c/templates/sections/schemas.c b/languages/c/templates/sections/schemas.c new file mode 100644 index 00000000..9295133c --- /dev/null +++ b/languages/c/templates/sections/schemas.c @@ -0,0 +1 @@ +${schema.list} diff --git a/languages/c/templates/sections/types.c b/languages/c/templates/sections/types.c new file mode 100644 index 00000000..9295133c --- /dev/null +++ b/languages/c/templates/sections/types.c @@ -0,0 +1 @@ +${schema.list} diff --git a/languages/c/templates/types/enum.cpp b/languages/c/templates/types/enum.cpp new file mode 100644 index 00000000..9c98f888 --- /dev/null +++ b/languages/c/templates/types/enum.cpp @@ -0,0 +1,4 @@ + /* ${title} ${description} */ + ENUM_CONVERSION_BEGIN(${info.Title}_${name}) + { ${info.TITLE}_${NAME}_${key}, _T("${value}") }, + ENUM_CONVERSION_END(${info.Title}_${name}) diff --git a/languages/c/templates/types/enum.h b/languages/c/templates/types/enum.h new file mode 100644 index 00000000..906d908d --- /dev/null +++ b/languages/c/templates/types/enum.h @@ -0,0 +1,4 @@ +/* ${title} ${description} */ +typedef enum { + ${info.TITLE}_${NAME}_${key}, +} ${info.Title}_${name}; diff --git a/languages/javascript/templates/methods/property.js b/languages/javascript/templates/methods/property.js index df748ecd..0b1e1359 100644 --- a/languages/javascript/templates/methods/property.js +++ b/languages/javascript/templates/methods/property.js @@ -1,4 +1,4 @@ function ${method.name}(${method.params.list}) { const callbackOrValue = arguments[${method.params.count}] - return Prop.prop('${info.title}', '${method.name}', { ${method.params} }, callbackOrValue, ${method.property.immutable}, ${method.property.readonly}, ${method.params.count}) + return Prop.prop('${info.title}', '${method.name}', { ${method.params.list} }, callbackOrValue, ${method.property.immutable}, ${method.property.readonly}, ${method.params.count}) } \ No newline at end of file diff --git a/src/macrofier/engine.mjs b/src/macrofier/engine.mjs index 39e97ea9..d0535d00 100644 --- a/src/macrofier/engine.mjs +++ b/src/macrofier/engine.mjs @@ -29,9 +29,9 @@ import isString from 'crocks/core/isString.js' import predicates from 'crocks/predicates/index.js' const { isObject, isArray, propEq, pathSatisfies, propSatisfies } = predicates -import { isRPCOnlyMethod, isProviderInterfaceMethod, getProviderInterface, getPayloadFromEvent, providerHasNoParameters, isTemporalSetMethod, hasMethodAttributes, getMethodAttributes, isEventMethodWithContext, getSemanticVersion, getSetterFor, getProvidedCapabilities, isPolymorphicPullMethod, hasPublicAPIs } from '../shared/modules.mjs' +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 } from '../shared/json-schema.mjs' +import { getLinkedSchemaPaths, getSchemaConstraints, isSchema, localizeDependencies, isDefinitionReferencedBySchema } from '../shared/json-schema.mjs' // util for visually debugging crocks ADTs const _inspector = obj => { @@ -46,20 +46,42 @@ const _inspector = obj => { // getMethodSignatureParams(method, module, options = { destination: 'file.txt' }) // getSchemaType(schema, module, options = { destination: 'file.txt', title: true }) // getSchemaShape(schema, module, options = { name: 'Foo', destination: 'file.txt' }) +// getJsonType(schema, module, options = { name: 'Foo', prefix: '', descriptions: false, level: 0 }) +// getSchemaInstantiation(schema, module, options = {type: 'params' | 'result' | 'callback.params'| 'callback.result' | 'callback.response'}) let types = { - getMethodSignature: ()=>null, - getMethodSignatureParams: ()=>null, - getSchemaShape: ()=>null, - getSchemaType: ()=>null + getMethodSignature: () => null, + getMethodSignatureParams: () => null, + getSchemaShape: () => null, + getSchemaType: () => null, + getJsonType: () => null, + getSchemaInstantiation: () => null } let config = { - copySchemasIntoModules: false + copySchemasIntoModules: false, + extractSubSchemas: false, + excludeDeclarations: false } const state = { - destination: undefined + destination: undefined, + section: undefined +} + +const capitalize = str => str[0].toUpperCase() + str.substr(1) + +const indent = (str, padding) => { + let first = true + return str.split('\n').map(line => { + if (first) { + first = false + return line + } + else { + return padding + line + } + }).join('\n') } const setTyper = (t) => { @@ -71,7 +93,7 @@ const setConfig = (c) => { } const getTemplate = (name, templates) => { - return templates[Object.keys(templates).find(k => k.startsWith(name + '.'))] || '' + return templates[Object.keys(templates).find(k => k === name)] || templates[Object.keys(templates).find(k => k.startsWith(name + '.'))] || '' } const getTemplateTypeForMethod = (method, type, templates) => { @@ -97,41 +119,55 @@ const getTemplateForExampleResult = (method, templates) => { return template || JSON.stringify(method.examples[0].result.value, null, '\t') } -const getLinkForSchema = (schema, json) => { +const getLinkForSchema = (schema, json, { name = '' } = {}) => { const dirs = config.createModuleDirectories const copySchemasIntoModules = config.copySchemasIntoModules - if (schema.schema) { - schema = schema.schema - } - - const type = types.getSchemaType(schema, json, { destination: state.destination }) + const type = types.getSchemaType(schema, json, { name: name, 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]) { return `#\$\{LINK:schema:${type}\}` } else { - const [group, schema] = Object.entries(json['x-schemas']).find( ([key, value]) => json['x-schemas'][key] && json['x-schemas'][key][type]) || [null, null] - if (group && schema) { - if (copySchemasIntoModules) { - return `#\$\{LINK:schema:${type}\}` + const [group, schema] = Object.entries(json['x-schemas']).find(([key, value]) => json['x-schemas'][key] && json['x-schemas'][key][type]) || [null, null] + if (group && schema) { + if (copySchemasIntoModules) { + return `#\$\{LINK:schema:${type}\}` + } + else { + const base = dirs ? '..' : '.' + if (dirs) { + return `${base}/${group}/schemas/#${type}` } else { - const base = dirs ? '..' : '.' - if (dirs) { - return `${base}/${group}/schemas/#${type}` - } - else { - return `${base}/schemas/${group}.md#${type}` - } + return `${base}/schemas/${group}.md#${type}` } } + } } return '#' } +const getComponentExternalSchema = (json) => { + let refSchemas = [] + if (json.components && json.components.schemas) { + Object.entries(json.components.schemas).forEach(([name, schema]) => { + let refs = getLinkedSchemaPaths(schema).map(path => getPathOr(null, path, schema)) + refs.map(ref => { + let title = '' + if (ref.includes('x-schemas')) { + if (ref.split('/')[2] !== json.info.title) { + title = ref.split('/')[2] + } + } + title && !refSchemas.includes(title) ? refSchemas.push(title) : null + }) + }) + } + return (refSchemas) +} // Maybe methods array of objects const getMethods = compose( @@ -247,6 +283,13 @@ const temporalSets = compose( getMethods ) +const callsMetrics = compose( + option([]), + map(filter(not(isExcludedMethod))), + map(filter(isCallsMetricsMethod)), + getMethods +) + const methodsWithXMethodsInResult = compose( option([]), map(filter(hasMethodAttributes)), @@ -313,31 +356,129 @@ const generateAggregateMacros = (openrpc, modules, templates, library) => Object library: library }) +const addContentDescriptorSubSchema = (descriptor, prefix, obj) => { + let title = '' + if (descriptor.schema.type === 'array' && descriptor.schema.items && descriptor.schema.items['$ref']) { + let refName = descriptor.schema.items['$ref'].split('/').pop() + title = refName.charAt(0).toUpperCase() + refName.substring(1) + '_ArrayType' + if (obj.components.schemas[title]) { + descriptor.schema = { + $ref: "#/components/schemas/" + title + } + return + } + } + else { + let descriptorName = capitalize(descriptor.name) + let prefixName = capitalize(prefix) + title = (prefixName !== descriptorName) ? prefixName + '_' +descriptorName : descriptorName + if (obj.components.schemas[title]) { + throw 'Generated name `' + title + '` already exists...' + } + } + obj.components.schemas[title] = descriptor.schema + obj.components.schemas[title].title = title + descriptor.schema = { + $ref: "#/components/schemas/" + title + } +} + +// only consider sub-objects, sub-array and sub-enums to be sub-schemas +const isSubSchema = (schema) => schema.type === 'object' || (schema.type === 'string' && schema.enum) || (schema.type === 'array' && schema.items) + +const promoteAndNameSubSchemas = (obj) => { + // 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 + obj.methods && obj.methods.forEach(method => { + if (!isExcludedMethod(method)) { + method.params && method.params.forEach(param => { + if (isSubSchema(param.schema)) { + addContentDescriptorSubSchema(param, method.name, obj) + } + }) + if (isSubSchema(method.result.schema)) { + addContentDescriptorSubSchema(method.result, method.name, obj) + } + } + }) + + // find non-primative sub-schemas of components.schemas and name/promote them + if (obj.components && obj.components.schemas) { + let more = true + while (more) { + more = false + Object.entries(obj.components.schemas).forEach(([key, schema]) => { + if ((schema.type === "object") && schema.properties) { + Object.entries(schema.properties).forEach(([name, propSchema]) => { + if (isSubSchema(propSchema)) { + more = true + const descriptor = { + name: name, + schema: propSchema + } + addContentDescriptorSubSchema(descriptor, key, obj) + schema.properties[name] = descriptor.schema + } + }) + } + }) + } + } + + return obj +} + const generateMacros = (obj, templates, languages, options = {}) => { + // for languages that don't support nested schemas, let's promote them to first-class schemas w/ titles + if (config.extractSubSchemas) { + obj = promoteAndNameSubSchemas(obj) + } + if (options.createPolymorphicMethods) { + let methods = [] + obj.methods && obj.methods.forEach(method => { + let polymorphicMethods = createPolymorphicMethods(method, obj) + if (polymorphicMethods.length > 1) { + polymorphicMethods.forEach(polymorphicMethod => { + methods.push(polymorphicMethod) + }) + } + else { + methods.push(method) + } + }) + obj.methods = methods + } // grab the options so we don't have to pass them from method to method Object.assign(state, options) - const imports = generateImports(obj, templates) + const imports = generateImports(obj, templates, { destination: (options.destination ? options.destination : '') }) const initialization = generateInitialization(obj, templates) - const enums = generateEnums(obj, templates) + const enums = generateEnums(obj, templates, { destination: (options.destination ? options.destination : '') }) const eventsEnum = generateEvents(obj, templates) const examples = generateExamples(obj, templates, languages) const allMethodsArray = generateMethods(obj, examples, templates) - const methodsArray = allMethodsArray.filter(m => !m.event && (!options.hideExcluded || !m.excluded)) - const eventsArray = allMethodsArray.filter(m => m.event && (!options.hideExcluded || !m.excluded)) - const declarationsArray = allMethodsArray.filter(m => m.declaration) + const methodsArray = allMethodsArray.filter(m => m.body && !m.event && (!options.hideExcluded || !m.excluded)) + const eventsArray = allMethodsArray.filter(m => m.body && m.event && (!options.hideExcluded || !m.excluded)) + const declarationsArray = allMethodsArray.filter(m => m.declaration && (!config.excludeDeclarations || (!options.hideExcluded || !m.excluded))) const declarations = declarationsArray.length ? getTemplate('/sections/declarations', templates).replace(/\$\{declaration\.list\}/g, declarationsArray.map(m => m.declaration).join('\n')) : '' const methods = methodsArray.length ? getTemplate('/sections/methods', templates).replace(/\$\{method.list\}/g, methodsArray.map(m => m.body).join('\n')) : '' const methodList = methodsArray.filter(m => m.body).map(m => m.name) + const providerInterfaces = generateProviderInterfaces(obj, templates) const events = eventsArray.length ? getTemplate('/sections/events', templates).replace(/\$\{event.list\}/g, eventsArray.map(m => m.body).join('\n')) : '' const eventList = eventsArray.map(m => makeEventName(m)) const defaults = generateDefaults(obj, templates) - const schemasArray = generateSchemas(obj, templates, { baseUrl: '' }).filter(s => (options.copySchemasIntoModules || !s.uri)) - const schemas = schemasArray.length ? getTemplate('/sections/schemas', templates).replace(/\$\{schema.list\}/g, schemasArray.map(s => s.body).join('\n')) : '' + const schemasArray = generateSchemas(obj, templates, { baseUrl: '', section: 'schemas' }).filter(s => (options.copySchemasIntoModules || !s.uri)) + const accessorsArray = generateSchemas(obj, templates, { baseUrl: '', section: 'accessors' }).filter(s => (options.copySchemasIntoModules || !s.uri)) + const schemas = schemasArray.length ? getTemplate('/sections/schemas', templates).replace(/\$\{schema.list\}/g, schemasArray.map(s => s.body).filter(body => body).join('\n')) : '' + const typesArray = schemasArray.length ? schemasArray.filter(x => !x.enum) : [] + const types = (typesArray.length ? getTemplate('/sections/types', templates).replace(/\$\{schema.list\}/g, typesArray.map(s => s.body).filter(body => body).join('\n')) : '') + + const accessors = (accessorsArray.length ? getTemplate('/sections/accessors', templates).replace(/\$\{schema.list\}/g, accessorsArray.map(s => s.body).filter(body => body).join('\n')) : '') const module = getTemplate('/codeblocks/module', templates) const macros = { @@ -349,10 +490,12 @@ const generateMacros = (obj, templates, languages, options = {}) => { eventsEnum, methods, methodList, + accessors, declarations, defaults, examples, schemas, + types, providerInterfaces, version: getSemanticVersion(obj), title: obj.info.title, @@ -378,18 +521,25 @@ const insertMacros = (fContents = '', macros = {}) => { if (macros.append) { fContents += '\n' + macros.module } - + const quote = config.operators ? config.operators.stringQuotation : '"' const or = config.operators ? config.operators.or : ' | ' + fContents = fContents.replace(/\$\{if\.types\}(.*?)\$\{end\.if\.types\}/gms, macros.types.trim() ? '$1' : '') + fContents = fContents.replace(/\$\{if\.schemas\}(.*?)\$\{end\.if\.schemas\}/gms, macros.schemas.trim() ? '$1' : '') + fContents = fContents.replace(/\$\{if\.declarations\}(.*?)\$\{end\.if\.declarations\}/gms, (macros.accessors.trim() || macros.declarations.trim() || macros.enums.trim()) ? '$1' : '') + fContents = fContents.replace(/\$\{if\.definitions\}(.*?)\$\{end\.if\.definitions\}/gms, (macros.accessors.trim() || macros.methods.trim() || macros.events.trim()) ? '$1' : '') + fContents = fContents.replace(/\$\{module.list\}/g, macros.module) fContents = fContents.replace(/[ \t]*\/\* \$\{METHODS\} \*\/[ \t]*\n/, macros.methods) + fContents = fContents.replace(/[ \t]*\/\* \$\{ACCESSORS\} \*\/[ \t]*\n/, macros.accessors.trimStart('\n')) fContents = fContents.replace(/[ \t]*\/\* \$\{DECLARATIONS\} \*\/[ \t]*\n/, macros.declarations) fContents = fContents.replace(/[ \t]*\/\* \$\{METHOD_LIST\} \*\/[ \t]*\n/, macros.methodList.join(',\n')) fContents = fContents.replace(/[ \t]*\/\* \$\{EVENTS\} \*\/[ \t]*\n/, macros.events) fContents = fContents.replace(/[ \t]*\/\* \$\{EVENT_LIST\} \*\/[ \t]*\n/, macros.eventList.join(',')) fContents = fContents.replace(/[ \t]*\/\* \$\{EVENTS_ENUM\} \*\/[ \t]*\n/, macros.eventsEnum) fContents = fContents.replace(/[ \t]*\/\* \$\{SCHEMAS\} \*\/[ \t]*\n/, macros.schemas) + fContents = fContents.replace(/[ \t]*\/\* \$\{TYPES\} \*\/[ \t]*\n/, macros.types) fContents = fContents.replace(/[ \t]*\/\* \$\{PROVIDERS\} \*\/[ \t]*\n/, macros.providerInterfaces) fContents = fContents.replace(/[ \t]*\/\* \$\{ENUMS\} \*\/[ \t]*\n/, macros.enums) fContents = fContents.replace(/[ \t]*\/\* \$\{IMPORTS\} \*\/[ \t]*\n/, macros.imports) @@ -401,9 +551,12 @@ const insertMacros = (fContents = '', macros = {}) => { fContents = fContents.replace(/\$\{minor\}/g, macros.version.minor) fContents = fContents.replace(/\$\{patch\}/g, macros.version.patch) fContents = fContents.replace(/\$\{info\.title\}/g, macros.title) + fContents = fContents.replace(/\$\{info\.title\.lowercase\}/g, macros.title.toLowerCase()) + fContents = fContents.replace(/\$\{info\.Title\}/g, capitalize(macros.title)) + fContents = fContents.replace(/\$\{info\.TITLE\}/g, macros.title.toUpperCase()) fContents = fContents.replace(/\$\{info\.description\}/g, macros.description) fContents = fContents.replace(/\$\{info\.version\}/g, macros.version.readable) - + if (macros.public) { fContents = fContents.replace(/\$\{if\.public\}(.*?)\$\{end\.if\.public\}/gms, '$1') } @@ -448,7 +601,7 @@ function insertTableofContents(content) { count[slug] = 0 } const link = '#' + slug + (count[slug] ? `-${count[slug]}` : '') - toc += ' ' + ' '.repeat(level-1) + `- [${title}](${link})\n` + toc += ' ' + ' '.repeat(level - 1) + `- [${title}](${link})\n` } } }).join('\n') @@ -461,7 +614,7 @@ function insertTableofContents(content) { const index = candidates.findIndex(line => line.indexOf(`- [${match[2]}](`) >= 0) let extra = '' - + // add '-1' to schemas when there's more than once match if (index > 0 && match[1] === 'schema') { extra = '-1' @@ -475,28 +628,44 @@ function insertTableofContents(content) { return content } +const convertEnumTemplate = (schema, templateName, templates) => { + let enumSchema = isArraySchema(schema) ? schema.items : schema + const template = getTemplate(templateName, templates).split('\n') + 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() + return template[i].replace(/\$\{key\}/g, safeName) + .replace(/\$\{value\}/g, value) + }).join('\n') + if (!templateName.includes(".cpp")) { + template[i] = template[i].replace(/,*$/, ''); + } + } + } + return template.join('\n') + .replace(/\$\{title\}/g, capitalize(schema.title)) + .replace(/\$\{description\}/g, schema.description ? ('- ' + schema.description) : '') + .replace(/\$\{name\}/g, schema.title) + .replace(/\$\{NAME\}/g, schema.title.toUpperCase()) +} + const enumFinder = compose( - filter(x => x.type === 'string' && Array.isArray(x.enum) && x.title), + filter(x => isEnum(x)), map(([_, val]) => val), filter(([_key, val]) => isObject(val)) ) -const generateEnums = (json, templates) => { +const generateEnums = (json, templates, options = { destination: '' }) => { + const suffix = options.destination.split('.').pop() return compose( option(''), + map(val => { + let template = val ? getTemplate(`/sections/enum.${suffix}`, templates) : val + return template ? template.replace(/\$\{schema.list\}/g, val.trimEnd()) : val + }), map(reduce((acc, val) => acc.concat(val).concat('\n'), '')), - map(map((schema) => { - const template = getTemplate('/types/enum', templates).split('\n') - for (var i = 0; i < template.length; i++) { - if (template[i].indexOf('${key}') >= 0) { - template[i] = schema.enum.map(value => { - const safeName = value.split(':').pop().replace(/[\.\-]/g, '_').replace(/\+/g, '_plus').replace(/([a-z])([A-Z0-9])/g, '$1_$2').toUpperCase() - return template[i].replace(/\$\{key\}/g, safeName).replace(/\$\{value\}/g, value) - }).join('\n') - } - } - return template.join('\n').replace(/\$\{name\}/g, schema.title) - })), + map(map((schema) => convertEnumTemplate(schema, suffix ? `/types/enum.${suffix}` : '/types/enum', templates))), map(enumFinder), getSchemas )(json) @@ -554,78 +723,104 @@ function generateDefaults(json = {}, templates) { return reducer(json) } +function sortSchemasByReference(schemas = []) { + let indexA = 0; + while (indexA < schemas.length) { + + let swapped = false + for (let indexB = indexA + 1; indexB < schemas.length; ++indexB) { + const bInA = isDefinitionReferencedBySchema('#/components/schemas/' + schemas[indexB][0], schemas[indexA][1]) + if ((isEnum(schemas[indexB][1]) && !isEnum(schemas[indexA][1])) || (bInA === true)) { + [schemas[indexA], schemas[indexB]] = [schemas[indexB], schemas[indexA]] + swapped = true + break + } + } + indexA = swapped ? indexA : ++indexA + } + return schemas +} + +const isArraySchema = x => x.type && x.type === 'array' && x.items + +const isEnum = x => { + let schema = isArraySchema(x) ? x.items : x + return schema.type && schema.type === 'string' && Array.isArray(schema.enum) && x.title +} + function generateSchemas(json, templates, options) { let results = [] - const schemas = json.definitions || (json.components && json.components.schemas) || {} + const schemas = JSON.parse(JSON.stringify(json.definitions || (json.components && json.components.schemas) || {})) - const generate = (name, schema, uri) => { + const generate = (name, schema, uri, { prefix = '' } = {}) => { // these are internal schemas used by the firebolt-openrpc tooling, and not meant to be used in code/doc generation if (['ListenResponse', 'ProviderRequest', 'ProviderResponse', 'FederatedResponse', 'FederatedRequest'].includes(name)) { return - } - + } let content = getTemplate('/schemas/default', templates) if (!schema.examples || schema.examples.length === 0) { - content = content.replace(/\$\{if\.examples\}.*?\{end\.if\.examples\}/gms, '') + content = content.replace(/\$\{if\.examples\}.*?\{end\.if\.examples\}/gms, '') } else { content = content.replace(/\$\{if\.examples\}(.*?)\{end\.if\.examples\}/gms, '$1') } if (!schema.description) { - content = content.replace(/\$\{if\.description\}.*?\{end\.if\.description\}/gms, '') + content = content.replace(/\$\{if\.description\}.*?\{end\.if\.description\}/gms, '') } else { content = content.replace(/\$\{if\.description\}(.*?)\{end\.if\.description\}/gms, '$1') } - const schemaShape = types.getSchemaShape(schema, json, { name, destination: state.destination }) + const schemaShape = types.getSchemaShape(schema, json, { name, prefix, destination: state.destination, section: options.section }) content = content - .replace(/\$\{schema.title\}/, (schema.title || name)) - .replace(/\$\{schema.description\}/, schema.description || '') - .replace(/\$\{schema.shape\}/, schemaShape) + .replace(/\$\{schema.title\}/, (schema.title || name)) + .replace(/\$\{schema.description\}/, schema.description || '') + .replace(/\$\{schema.shape\}/, schemaShape) if (schema.examples) { - content = content.replace(/\$\{schema.example\}/, schema.examples.map(ex => JSON.stringify(ex, null, ' ')).join('\n\n')) + content = content.replace(/\$\{schema.example\}/, schema.examples.map(ex => JSON.stringify(ex, null, ' ')).join('\n\n')) } let seeAlso = getRelatedSchemaLinks(schema, json, templates, options) if (seeAlso) { - content = content.replace(/\$\{schema.seeAlso\}/, '\n\n' + seeAlso) + content = content.replace(/\$\{schema.seeAlso\}/, '\n\n' + seeAlso) } else { - content = content.replace(/.*\$\{schema.seeAlso\}/, '') + content = content.replace(/.*\$\{schema.seeAlso\}/, '') } + content = content.trim().length ? content.trimEnd() : content.trim() + + const isEnum = x => x.type === 'string' && Array.isArray(x.enum) && x.title const result = uri ? { uri: uri, name: schema.title || name, - body: content + body: content, + enum: isEnum(schema) } : { name: schema.title || name, - body: content + body: content, + enum: isEnum(schema) } results.push(result) } + let list = [] + // schemas may be 1 or 2 levels deeps - Object.entries(schemas).forEach( ([name, schema]) => { + Object.entries(schemas).forEach(([name, schema]) => { if (isSchema(schema)) { - generate(name, schema) - } - else if (typeof schema === 'object') { - const uri = schema.uri - Object.entries(schema).forEach( ([name, schema]) => { - if (name !== 'uri') { - generate(name, schema, uri) - } - }) + list.push([name, schema]) } }) + list = sortSchemasByReference(list) + list.forEach(item => generate(...item)) + return results } @@ -637,20 +832,20 @@ 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) let links = getLinkedSchemaPaths(schema) - .map(path => getPathOr(null, path, schema)) - .filter(path => seen.hasOwnProperty(path) ? false : (seen[path] = true)) - .map(path => path.substring(2).split('/')) - .map(path => getPathOr(null, path, json)) - .filter(schema => schema.title) - .map(schema => '[' + types.getSchemaType(schema, json, { destination: state.destination }) + '](' + getLinkForSchema(schema, json, true) + ')') // need full module here, not just the schema - .filter(link => link) - .join('\n') + .map(path => getPathOr(null, path, schema)) + .filter(path => seen.hasOwnProperty(path) ? false : (seen[path] = true)) + .map(path => path.substring(2).split('/')) + .map(path => getPathOr(null, path, json)) + .filter(schema => schema.title) + .map(schema => '[' + types.getSchemaType(schema, json, { name: schema.title, destination: state.destination, section: state.section }) + '](' + getLinkForSchema(schema, json, { name: schema.title }) + ')') // need full module here, not just the schema + .filter(link => link) + .join('\n') return links } -const generateImports = (json, templates) => { - let imports = getTemplate('/imports/default', templates) +const generateImports = (json, templates, options = { destination: '' }) => { + let imports = '' if (rpcMethodsOrEmptyArray(json).length) { imports += getTemplate('/imports/rpc', templates) @@ -679,7 +874,26 @@ const generateImports = (json, templates) => { if (methodsWithXMethodsInResult(json).length) { imports += getTemplate('/imports/x-method', templates) } + const suffix = options.destination.split('.').pop() + const prefix = options.destination.split('/').pop().split('_')[0].toLowerCase() + + if (callsMetrics(json).length) { + imports += getTemplate(suffix ? `/imports/calls-metrics.${suffix}` : '/imports/calls-metrics', templates) + } + + let template = prefix ? getTemplate(`/imports/default.${prefix}`, templates) : '' + if (!template) { + template = getTemplate(suffix ? `/imports/default.${suffix}` : '/imports/default', templates) + } + + if (json['x-schemas'] && Object.keys(json['x-schemas']).length > 0 && !json.info['x-uri-titles']) { + imports += Object.keys(json['x-schemas']).map(shared => template.replace(/\$\{info.title.lowercase\}/g, shared.toLowerCase())).join('') + } + let componentExternalSchema = getComponentExternalSchema(json) + if (componentExternalSchema.length && json.info['x-uri-titles']) { + imports += componentExternalSchema.map(shared => template.replace(/\$\{info.title.lowercase\}/g, shared.toLowerCase())).join('') + } return imports } @@ -742,13 +956,13 @@ function generateExamples(json = {}, mainTemplates = {}, languages = {}) { examples[method.name] = method.examples.map(example => ({ json: example, value: example.result.value, - languages: Object.fromEntries(Object.entries(languages).map( ([lang, templates]) => ([lang, { + languages: Object.fromEntries(Object.entries(languages).map(([lang, templates]) => ([lang, { langcode: templates['__config'].langcode, code: getTemplateForExample(method, templates) - .replace(/\$\{rpc\.example\.params\}/g, JSON.stringify(Object.fromEntries(example.params.map(param => [param.name, param.value])))), + .replace(/\$\{rpc\.example\.params\}/g, JSON.stringify(Object.fromEntries(example.params.map(param => [param.name, param.value])))), result: getTemplateForExampleResult(method, templates) - .replace(/\$\{example\.result\}/g, JSON.stringify(example.result.value, null, '\t')) - .replace(/\$\{example\.result\.item\}/g, Array.isArray(example.result.value) ? JSON.stringify(example.result.value[0], null, '\t') : ''), + .replace(/\$\{example\.result\}/g, JSON.stringify(example.result.value, null, '\t')) + .replace(/\$\{example\.result\.item\}/g, Array.isArray(example.result.value) ? JSON.stringify(example.result.value[0], null, '\t') : ''), template: lang === 'JSON-RPC' ? getTemplate('/examples/jsonrpc', mainTemplates) : getTemplateForExample(method, mainTemplates) // getTemplate('/examples/default', mainTemplates) }]))) })) @@ -758,7 +972,7 @@ function generateExamples(json = {}, mainTemplates = {}, languages = {}) { examples[method.name] = examples[method.name].map(example => ({ json: example.json, value: example.value, - languages: Object.fromEntries(Object.entries(example.languages).filter( ([k, v]) => k === 'JSON-RPC')) + languages: Object.fromEntries(Object.entries(example.languages).filter(([k, v]) => k === 'JSON-RPC')) })) } @@ -769,7 +983,7 @@ function generateExamples(json = {}, mainTemplates = {}, languages = {}) { example.languages['JSON-RPC'].code = JSON.stringify(JSON.parse(example.languages['JSON-RPC'].code), null, '\t') example.languages['JSON-RPC'].result = JSON.stringify(JSON.parse(example.languages['JSON-RPC'].result), null, '\t') } - catch (error) {} + catch (error) { } } }) }) @@ -834,12 +1048,12 @@ function generateMethods(json = {}, examples = {}, templates = {}) { body: getTemplate('/methods/once', templates), declaration: getTemplate('/declarations/once', templates) }) - + results.push({ name: "clear", body: getTemplate('/methods/clear', templates), declaration: getTemplate('/declarations/clear', templates) - }) + }) } results.sort((a, b) => a.name.localeCompare(b.name)) @@ -848,7 +1062,7 @@ function generateMethods(json = {}, examples = {}, templates = {}) { } // TODO: this is called too many places... let's reduce that to just generateMethods -function insertMethodMacros(template, methodObj, json, templates, examples={}) { +function insertMethodMacros(template, methodObj, json, templates, examples = {}) { const moduleName = getModuleName(json) const info = { @@ -879,12 +1093,15 @@ function insertMethodMacros(template, methodObj, json, templates, examples={}) { } } + const paramDelimiter = config.operators ? config.operators.paramDelimiter : ', ' + const temporalItemName = isTemporalSetMethod(methodObj) ? methodObj.result.schema.items && methodObj.result.schema.items.title || 'Item' : '' const temporalAddName = isTemporalSetMethod(methodObj) ? `on${temporalItemName}Available` : '' const temporalRemoveName = isTemporalSetMethod(methodObj) ? `on${temporalItemName}Unvailable` : '' - const params = methodObj.params && methodObj.params.length ? getTemplate('/sections/parameters', templates) + methodObj.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, methodObj, json)).join('') : '' + const params = methodObj.params && methodObj.params.length ? getTemplate('/sections/parameters', templates) + methodObj.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, methodObj, json)).join(paramDelimiter) : '' const paramsRows = methodObj.params && methodObj.params.length ? methodObj.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, methodObj, json)).join('') : '' const paramsAnnotations = methodObj.params && methodObj.params.length ? methodObj.params.map(p => insertParameterMacros(getTemplate('/parameters/annotations', templates), p, methodObj, json)).join('') : '' + const paramsJson = methodObj.params && methodObj.params.length ? methodObj.params.map(p => insertParameterMacros(getTemplate('/parameters/json', templates), p, methodObj, json)).join('') : '' const deprecated = methodObj.tags && methodObj.tags.find(t => t.name === 'deprecated') const deprecation = deprecated ? deprecated['x-since'] ? `since version ${deprecated['x-since']}` : '' : '' @@ -892,13 +1109,13 @@ function insertMethodMacros(template, methodObj, json, templates, examples={}) { const capabilities = getTemplate('/sections/capabilities', templates) + insertCapabilityMacros(getTemplate('/capabilities/default', templates), methodObj.tags.find(t => t.name === "capabilities"), methodObj, json) const result = JSON.parse(JSON.stringify(methodObj.result)) - const event = JSON.parse(JSON.stringify(methodObj)) - - if (isEventMethod(methodObj)) { + const event = isEventMethod(methodObj) ? JSON.parse(JSON.stringify(methodObj)) : '' + + if (event) { result.schema = JSON.parse(JSON.stringify(getPayloadFromEvent(methodObj))) event.result.schema = getPayloadFromEvent(event) event.params = event.params.filter(p => p.name !== 'listen') - } + } const eventParams = event.params && event.params.length ? getTemplate('/sections/parameters', templates) + event.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, event, json)).join('') : '' const eventParamsRows = event.params && event.params.length ? event.params.map(p => insertParameterMacros(getTemplate('/parameters/default', templates), p, event, json)).join('') : '' @@ -916,14 +1133,28 @@ function insertMethodMacros(template, methodObj, json, templates, examples={}) { const subscriberTemplate = (subscriber ? insertMethodMacros(getTemplate('/codeblocks/subscriber', templates), subscriber, json, templates, examples) : '') 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, json).params[1].schema : null - const pullsParams = (puller || pullsFor) ? localizeDependencies(getPayloadFromEvent(puller || methodObj), json, null, {mergeAllOfs: true}).properties.parameters : null - const pullsResultType = pullsResult && types.getSchemaShape(pullsResult, json, { destination: state.destination }) - const pullsForType = pullsResult && types.getSchemaType(pullsResult, json, { destination: state.destination }) - const pullsParamsType = pullsParams ? types.getSchemaShape(pullsParams, json, { destination: state.destination }) : '' - + const pullsParams = (puller || pullsFor) ? localizeDependencies(getPayloadFromEvent(puller || methodObj), json, null, { mergeAllOfs: true }).properties.parameters : null + const pullsResultType = pullsResult && types.getSchemaShape(pullsResult, json, { destination: state.destination, section: state.section }) + const pullsForType = pullsResult && types.getSchemaType(pullsResult, json, { destination: state.destination, section: state.section }) + + const pullsForJsonType = pullsResult ? types.getJsonType(pullsResult, json, { name: pullsResult.name }) : '' + const pullsParamsType = pullsParams ? types.getSchemaShape(pullsParams, json, { destination: state.destination, section: state.section }) : '' + const pullsForParamType = pullsParams ? types.getSchemaType(pullsParams, json, { destination: state.destination, section: state.section }) : '' + const pullsForParamJsonType = pullsParams ? types.getJsonType(pullsParams, json, { name: pullsParams.title }) : '' + const pullsEventParamName = event ? types.getSchemaInstantiation(event.result, json, event.name, { instantiationType: 'pull.param.name' }) : '' + + const serializedParams = types.getSchemaInstantiation(methodObj, json, methodObj.name, { instantiationType: 'params' }) + const resultInst = types.getSchemaInstantiation(result.schema, json, result.name, { instantiationType: 'result' } ) + const serializedEventParams = event ? indent(types.getSchemaInstantiation(event, json, event.name, { instantiationType: 'params' }), ' ') : '' + const callbackSerializedParams = event ? types.getSchemaInstantiation(event, json, event.name, { instantiationType: 'callback.params', prefix: method.alternative }) : '' + const callbackResultInst = event ? types.getSchemaInstantiation(event, json, event.name, { instantiationType: 'callback.result', prefix: method.alternative }) : '' + const callbackResponseInst = event ? types.getSchemaInstantiation(event, json, event.name, { instantiationType: 'callback.response', prefix: method.alternative }) : '' + const callbackResultJsonType = event && result.schema ? types.getJsonType(result.schema, json, { name: result.name, prefix: method.alternative }) : '' + const resultType = result.schema ? types.getSchemaType(result.schema, json, { name: result.name }) : '' + const resultJsonType = result.schema ? types.getJsonType(result.schema, json, { name: result.name }) : '' + let seeAlso = '' - - if (isPolymorphicPullMethod(methodObj)) { + if (isPolymorphicPullMethod(methodObj) && pullsForType) { seeAlso = `See also: [${pullsForType}](#${pullsForType.toLowerCase()}-1)` // this assumes the schema will be after the method... } else if (methodObj.tags.find(t => t.name === 'polymorphic-pull')) { @@ -932,29 +1163,36 @@ function insertMethodMacros(template, methodObj, json, templates, examples={}) { } if (isTemporalSetMethod(methodObj)) { - itemName = result.schema.items.title || 'item' - itemName = itemName.charAt(0).toLowerCase() + itemName.substring(1) - itemType = types.getSchemaType(result.schema.items, json, { destination: state.destination }) + itemName = result.schema.items.title || 'item' + itemName = itemName.charAt(0).toLowerCase() + itemName.substring(1) + itemType = types.getSchemaType(result.schema.items, json, { destination: state.destination, section: state.section }) } template = insertExampleMacros(template, examples[methodObj.name] || [], methodObj, json, templates) template = template.replace(/\$\{method\.name\}/g, method.name) + .replace(/\$\{method\.rpc\.name\}/g, methodObj.title || methodObj.name) .replace(/\$\{method\.summary\}/g, methodObj.summary) .replace(/\$\{method\.description\}/g, methodObj.description - || methodObj.summary) + || methodObj.summary) // Parameter stuff .replace(/\$\{method\.params\}/g, params) .replace(/\$\{method\.params\.table\.rows\}/g, paramsRows) .replace(/\$\{method\.params\.annotations\}/g, paramsAnnotations) + .replace(/\$\{method\.params\.json\}/g, paramsJson) .replace(/\$\{method\.params\.list\}/g, method.params) .replace(/\$\{method\.params\.array\}/g, JSON.stringify(methodObj.params.map(p => p.name))) .replace(/\$\{method\.params\.count}/g, methodObj.params ? methodObj.params.length : 0) - .replace(/\$\{if\.params\}(.*?)\$\{end\.if\.params\}/g, method.params.length ? '$1' : '') - .replace(/\$\{if\.context\}(.*?)\$\{end\.if\.context\}/gms, event.params.length ? '$1' : '') + .replace(/\$\{if\.params\}(.*?)\$\{end\.if\.params\}/gms, method.params.length ? '$1' : '') + .replace(/\$\{if\.result\}(.*?)\$\{end\.if\.result\}/gms, resultType ? '$1' : '') + .replace(/\$\{if\.params\.empty\}(.*?)\$\{end\.if\.params\.empty\}/gms, method.params.length === 0 ? '$1' : '') + .replace(/\$\{if\.signature\.empty\}(.*?)\$\{end\.if\.signature\.empty\}/gms, (method.params.length === 0 && resultType === '') ? '$1' : '') + .replace(/\$\{if\.context\}(.*?)\$\{end\.if\.context\}/gms, event && event.params.length ? '$1' : '') + .replace(/\$\{method\.params\.serialization\}/g, serializedParams) + .replace(/\$\{method\.params\.serialization\.with\.indent\}/g, indent(serializedParams, ' ')) // Typed signature stuff - .replace(/\$\{method\.signature\}/g, types.getMethodSignature(methodObj, json, { isInterface: false, destination: state.destination })) - .replace(/\$\{method\.signature\.params\}/g, types.getMethodSignatureParams(methodObj, json, { destination: state.destination })) + .replace(/\$\{method\.signature\}/g, types.getMethodSignature(methodObj, json, { isInterface: false, destination: state.destination, section: state.section })) + .replace(/\$\{method\.signature\.params\}/g, types.getMethodSignatureParams(methodObj, json, { destination: state.destination, section: state.section })) .replace(/\$\{method\.context\}/g, method.context.join(', ')) .replace(/\$\{method\.context\.array\}/g, JSON.stringify(method.context)) .replace(/\$\{method\.context\.count}/g, method.context ? method.context.length : 0) @@ -963,8 +1201,17 @@ function insertMethodMacros(template, methodObj, json, templates, examples={}) { .replace(/\$\{event\.name\}/g, method.name.toLowerCase()[2] + method.name.substr(3)) .replace(/\$\{event\.params\}/g, eventParams) .replace(/\$\{event\.params\.table\.rows\}/g, eventParamsRows) - .replace(/\$\{event\.signature\.params\}/g, types.getMethodSignatureParams(event, json, { destination: state.destination })) + .replace(/\$\{if\.event\.params\}(.*?)\$\{end\.if\.event\.params\}/gms, event && event.params.length ? '$1' : '') + .replace(/\$\{event\.signature\.params\}/g, event ? types.getMethodSignatureParams(event, json, { destination: state.destination, section: state.section }) : '') + .replace(/\$\{event\.signature\.callback\.params\}/g, event ? types.getMethodSignatureParams(event, json, { destination: state.destination, section: state.section, callback: true }) : '') + .replace(/\$\{event\.params\.serialization\}/g, serializedEventParams) + .replace(/\$\{event\.callback\.params\.serialization\}/g, callbackSerializedParams) + .replace(/\$\{event\.callback\.result\.instantiation\}/g, callbackResultInst) + .replace(/\$\{event\.callback\.response\.instantiation\}/g, callbackResponseInst) + .replace(/\$\{info\.title\.lowercase\}/g, info.title.toLowerCase()) .replace(/\$\{info\.title\}/g, info.title) + .replace(/\$\{info\.Title\}/g, capitalize(info.title)) + .replace(/\$\{info\.TITLE\}/g, info.title.toUpperCase()) .replace(/\$\{method\.property\.immutable\}/g, hasTag(methodObj, 'property:immutable')) .replace(/\$\{method\.property\.readonly\}/g, !getSetterFor(methodObj.name, json)) .replace(/\$\{method\.temporalset\.add\}/g, temporalAddName) @@ -976,23 +1223,33 @@ function insertMethodMacros(template, methodObj, json, templates, examples={}) { .replace(/\$\{method\.capabilities\}/g, capabilities) .replace(/\$\{method\.result\.name\}/g, result.name) .replace(/\$\{method\.result\.summary\}/g, result.summary) - .replace(/\$\{method\.result\.link\}/g, getLinkForSchema(result, json)) //, baseUrl: options.baseUrl - .replace(/\$\{method\.result\.type\}/g, types.getSchemaType(result, json, {title: true, asPath: false, destination: state.destination })) //, baseUrl: options.baseUrl - .replace(/\$\{event\.result\.type\}/, isEventMethod(methodObj) ? types.getSchemaType(result, json, { destination: state.destination, event: true, description: methodObj.result.summary, asPath: false }): '') //, baseUrl: options.baseUrl - .replace(/\$\{method\.result\}/g, generateResult(result.schema, json, templates)) - .replace(/\$\{method\.example\.value\}/g, JSON.stringify(methodObj.examples[0].result.value)) + .replace(/\$\{method\.result\.link\}/g, getLinkForSchema(result.schema, json, { name: result.name })) //, baseUrl: options.baseUrl + .replace(/\$\{method\.result\.type\}/g, types.getSchemaType(result.schema, json, { name: result.name, title: true, asPath: false, destination: state.destination, resultSchema: true })) //, baseUrl: options.baseUrl + .replace(/\$\{method\.result\.json\}/, types.getJsonType(result.schema, json, { name: result.name, destination: state.destination, section: state.section, code: false, link: false, title: true, asPath: false, expandEnums: false })) + .replace(/\$\{event\.result\.type\}/, isEventMethod(methodObj) ? types.getSchemaType(result.schema, json, { name: result.name, prefix: method.alternative, destination: state.destination, event: true, description: methodObj.result.summary, asPath: false }) : '') + .replace(/\$\{event\.result\.json\.type\}/g, callbackResultJsonType) + .replace(/\$\{event\.pulls\.param\.name\}/g, pullsEventParamName) + .replace(/\$\{method\.result\}/g, generateResult(result.schema, json, templates, { name: result.name })) + .replace(/\$\{method\.result\.json\.type\}/g, resultJsonType) + .replace(/\$\{method\.result\.instantiation\}/g, resultInst) + .replace(/\$\{method\.result\.instantiation\.with\.indent\}/g, indent(resultInst, ' ')) + .replace(/\$\{method\.example\.value\}/g, JSON.stringify(methodObj.examples[0].result.value)) .replace(/\$\{method\.alternative\}/g, method.alternative) - .replace(/\$\{method\.alternative.link\}/g, '#'+(method.alternative || "").toLowerCase()) - .replace(/\$\{method\.pulls\.for\}/g, pullsFor ? pullsFor.name : '' ) + .replace(/\$\{method\.alternative.link\}/g, '#' + (method.alternative || "").toLowerCase()) + .replace(/\$\{method\.pulls\.for\}/g, pullsFor ? pullsFor.name : '') .replace(/\$\{method\.pulls\.type\}/g, pullsForType) + .replace(/\$\{method\.pulls\.json\.type\}/g, pullsForJsonType) .replace(/\$\{method\.pulls\.result\}/g, pullsResultType) .replace(/\$\{method\.pulls\.params.type\}/g, pullsParams ? pullsParams.title : '') .replace(/\$\{method\.pulls\.params\}/g, pullsParamsType) - .replace(/\$\{method\.setter\.for\}/g, setterFor ) + .replace(/\$\{method\.pulls\.param\.type\}/g, pullsForParamType) + .replace(/\$\{method\.pulls\.param\.json.type\}/g, pullsForParamJsonType) + .replace(/\$\{method\.setter\.for\}/g, setterFor) .replace(/\$\{method\.puller\}/g, pullerTemplate) // must be last!! .replace(/\$\{method\.setter\}/g, setterTemplate) // must be last!! .replace(/\$\{method\.subscriber\}/g, subscriberTemplate) // must be last!! + if (method.deprecated) { template = template.replace(/\$\{if\.deprecated\}(.*?)\$\{end\.if\.deprecated\}/gms, '$1') } @@ -1004,10 +1261,10 @@ function insertMethodMacros(template, methodObj, json, templates, examples={}) { const matches = [...template.matchAll(/\$\{method\.params\[([0-9]+)\]\.type\}/g)] matches.forEach(match => { const index = parseInt(match[1]) - template = template.replace(/\$\{method\.params\[([0-9]+)\]\.type\}/g, types.getSchemaType(methodObj.params[index], json, { destination: state.destination })) + template = template.replace(/\$\{method\.params\[([0-9]+)\]\.type\}/g, types.getSchemaType(methodObj.params[index].schema, json, { destination: state.destination })) template = template.replace(/\$\{method\.params\[([0-9]+)\]\.name\}/g, methodObj.params[index].name) }) - + // Note that we do this twice to ensure all recursive macros are resolved template = insertExampleMacros(template, examples[methodObj.name] || [], methodObj, json, templates) @@ -1045,38 +1302,38 @@ function insertExampleMacros(template, examples, method, json, templates) { let indent = ' '.repeat(json.info.title.length + method.name.length + 2) let params = formatParams(method.params, ', ') if (params.length + indent > 80) { - params = formatParams(method.params, ',\n', true) - params = params.split('\n') - let first = params.shift() - params = params.map(p => indent + p) - params.unshift(first) - params = params.join('\n') + params = formatParams(method.params, ',\n', true) + params = params.split('\n') + let first = params.shift() + params = params.map(p => indent + p) + params.unshift(first) + params = params.join('\n') } languageContent = languageContent - .replace(/\$\{example\.code\}/g, language.code) - .replace(/\$\{example\.name\}/g, example.json.name) - .replace(/\$\{example\.language\}/g, name) - .replace(/\$\{example\.langcode\}/g, language.langcode) - - .replace(/\$\{method\.result\.name\}/g, method.result.name) - .replace(/\$\{method\.name\}/g, method.name) - .replace(/\$\{example\.params\}/g, params) - .replace(/\$\{example\.result\}/g, language.result) - .replace(/\$\{example\.result\.item\}/g, Array.isArray(example.json.result.value) ? JSON.stringify(example.json.result.value[0], null, '\t') : '') - .replace(/\$\{module\}/g, json.info.title) + .replace(/\$\{example\.code\}/g, language.code) + .replace(/\$\{example\.name\}/g, example.json.name) + .replace(/\$\{example\.language\}/g, name) + .replace(/\$\{example\.langcode\}/g, language.langcode) + + .replace(/\$\{method\.result\.name\}/g, method.result.name) + .replace(/\$\{method\.name\}/g, method.name) + .replace(/\$\{example\.params\}/g, params) + .replace(/\$\{example\.result\}/g, language.result) + .replace(/\$\{example\.result\.item\}/g, Array.isArray(example.json.result.value) ? JSON.stringify(example.json.result.value[0], null, '\t') : '') + .replace(/\$\{module\}/g, json.info.title) const matches = [...languageContent.matchAll(/\$\{method\.params\[([0-9]+)\]\.example\.value\}/g)] matches.forEach(match => { const paramIndex = parseInt(match[1]) let indent = 0 - while (match.index-indent >= 0 && match.input[match.index-indent] !== '\n') { + while (match.index - indent >= 0 && match.input[match.index - indent] !== '\n') { indent++ } - const value = JSON.stringify(method.examples[index].params[paramIndex].value, null, '\t').split('\n').map( (line, i) => i > 0 ? ' '.repeat(indent) + line : line).join('\n') + const value = JSON.stringify(method.examples[index].params[paramIndex].value, null, '\t').split('\n').map((line, i) => i > 0 ? ' '.repeat(indent) + line : line).join('\n') languageContent = languageContent.replace(/\$\{method\.params\[([0-9]+)\]\.example\.value\}/g, value) }) - + if (originator) { const originalExample = originator.examples.length > index ? originator.examples[index] : originator.examples[0] @@ -1084,10 +1341,10 @@ function insertExampleMacros(template, examples, method, json, templates) { matches.forEach(match => { const paramIndex = parseInt(match[1]) let indent = 0 - while (match.index-indent >= 0 && match.input[match.index-indent] !== '\n') { + while (match.index - indent >= 0 && match.input[match.index - indent] !== '\n') { indent++ } - const value = JSON.stringify(originalExample.params[paramIndex].value, null, '\t').split('\n').map( (line, i) => i > 0 ? ' '.repeat(indent) + line : line).join('\n') + const value = JSON.stringify(originalExample.params[paramIndex].value, null, '\t').split('\n').map((line, i) => i > 0 ? ' '.repeat(indent) + line : line).join('\n') languageContent = languageContent.replace(/\$\{originator\.params\[([0-9]+)\]\.example\.value\}/g, value) }) } @@ -1107,69 +1364,73 @@ function insertExampleMacros(template, examples, method, json, templates) { return template.replace(/\$\{method\.examples\}/g, content) } -function generateResult(result, json, templates) { - const type = types.getSchemaType(result, json, { destination: state.destination }) +function generateResult(result, json, templates, { name = '' } = {}) { + + const type = types.getSchemaType(result, json, { name: name, destination: state.destination, section: state.section }) if (result.type === 'object' && result.properties) { let content = getTemplate('/types/object', templates).split('\n') - for (var i=0; i= 0) { content[i] = Object.entries(result.properties).map(([title, property]) => insertSchemaMacros(content[i], title, property, json)).join('\n') } } - return insertSchemaMacros(content.join('\n'), result.title, result, json) + return insertSchemaMacros(content.join('\n'), name, result, json) } else if (type === 'string' && Array.isArray(result.enum)) { - return insertSchemaMacros(getTemplate('/types/enum', templates), result, json) + return insertSchemaMacros(getTemplate('/types/enum', templates), name, result, json) } else if (result.$ref) { - const link = getLinkForSchema(result, json) + const link = getLinkForSchema(result, json, { name: name }) // if we get a real link use it if (link !== '#') { - return `[${types.getSchemaType(result, json, { destination: state.destination })}](${link})` + return `[${types.getSchemaType(result, json, { destination: state.destination, section: state.section })}](${link})` } // otherwise this was a schema with no title, and we'll just copy it here else { const schema = localizeDependencies(result, json) return getTemplate('/types/default', templates) - .replace(/\$\{type\}/, types.getSchemaShape(schema, json, { name: result.$ref.split("/").pop()})) + .replace(/\$\{type\}/, types.getSchemaShape(schema, json, { name: result.$ref.split("/").pop() })) } } else { - return insertSchemaMacros(getTemplate('/types/default', templates), result.title, result, json) + return insertSchemaMacros(getTemplate('/types/default', templates), name, result, json) } } function insertSchemaMacros(template, title, schema, module) { return template.replace(/\$\{property\}/g, title) - .replace(/\$\{type\}/g, types.getSchemaType(schema, module, { destination: state.destination, code: false })) - .replace(/\$\{type.link\}/g, getLinkForSchema(schema, module)) - .replace(/\$\{description\}/g, schema.description || '') - .replace(/\$\{name\}/g, title || '') + .replace(/\$\{type\}/g, types.getSchemaType(schema, module, { name: title, destination: state.destination, section: state.section, code: false })) + .replace(/\$\{type.link\}/g, getLinkForSchema(schema, module, { name: title })) + .replace(/\$\{description\}/g, schema.description || '') + .replace(/\$\{name\}/g, title || '') } function insertParameterMacros(template, param, method, module) { -//| `${method.param.name}` | ${method.param.type} | ${method.param.required} | ${method.param.summary} ${method.param.constraints} | + //| `${method.param.name}` | ${method.param.type} | ${method.param.required} | ${method.param.summary} ${method.param.constraints} | let constraints = getSchemaConstraints(param, module) - let type = types.getSchemaType(param, module, { destination: state.destination, code: false, link: false, title: true, asPath: false, expandEnums: false }) //baseUrl: options.baseUrl - let typeLink = getLinkForSchema(param, module) + let type = types.getSchemaType(param.schema, module, { name: param.name, destination: state.destination, section: state.section, code: false, link: false, title: true, asPath: false, expandEnums: false }) //baseUrl: options.baseUrl + let typeLink = getLinkForSchema(param.schema, module, { name: param.name }) + let jsonType = types.getJsonType(param.schema, module, { name: param.name, destination: state.destination, section: state.section, code: false, link: false, title: true, asPath: false, expandEnums: false }) if (constraints && type) { - constraints = '
' + constraints + constraints = '
' + constraints } return template - .replace(/\$\{method.param.name\}/, param.name) - .replace(/\$\{method.param.summary\}/, param.summary || '') - .replace(/\$\{method.param.required\}/, param.required || 'false') - .replace(/\$\{method.param.type\}/, type) //getType(param)) - .replace(/\$\{method.param.link\}/, getLinkForSchema(param, module)) //getType(param)) - .replace(/\$\{method.param.constraints\}/, constraints) //getType(param)) + .replace(/\$\{method.param.name\}/g, param.name) + .replace(/\$\{method.param.Name\}/g, param.name[0].toUpperCase() + param.name.substring(1)) + .replace(/\$\{method.param.summary\}/g, param.summary || '') + .replace(/\$\{method.param.required\}/g, param.required || 'false') + .replace(/\$\{method.param.type\}/g, type) + .replace(/\$\{json.param.type\}/g, jsonType) + .replace(/\$\{method.param.link\}/g, getLinkForSchema(param.schema, module, { name: param.name })) //getType(param)) + .replace(/\$\{method.param.constraints\}/g, constraints) //getType(param)) } function insertCapabilityMacros(template, capabilities, method, module) { @@ -1179,18 +1440,18 @@ function insertCapabilityMacros(template, capabilities, method, module) { roles.forEach(role => { if (capabilities[role] && capabilities[role].length) { content.push(template.replace(/\$\{role\}/g, role.split('-').pop()) - .replace(/\$\{capability\}/g, capabilities[role].join('
'))) // Warning, hack! + .replace(/\$\{capability\}/g, capabilities[role].join('
'))) // Warning, hack! } }) if (capabilities['x-provides']) { content.push(template.replace(/\$\{role\}/g, 'provides') - .replace(/\$\{capability\}/g, capabilities['x-provides'])) -} + .replace(/\$\{capability\}/g, capabilities['x-provides'])) + } return content.join() } - + function generateProviderInterfaces(json, templates) { const interfaces = getProvidedCapabilities(json) let template = getTemplate('/sections/provider-interfaces', templates) @@ -1204,9 +1465,8 @@ function generateProviderInterfaces(json, templates) { } function insertProviderInterfaceMacros(template, capability, moduleJson = {}, templates) { - const iface = getProviderInterface(capability, moduleJson, { destination: state.destination })//.map(method => { method.name = method.name.charAt(9).toLowerCase() + method.name.substr(10); return method } ) + const iface = getProviderInterface(capability, moduleJson, { destination: state.destination, section: state.section })//.map(method => { method.name = method.name.charAt(9).toLowerCase() + method.name.substr(10); return method } ) - const capitalize = str => str[0].toUpperCase() + str.substr(1) const uglyName = capability.split(":").slice(-2).map(capitalize).reverse().join('') + "Provider" let name = iface.length === 1 ? iface[0].name.charAt(0).toUpperCase() + iface[0].name.substr(1) + "Provider" : uglyName @@ -1217,96 +1477,83 @@ function insertProviderInterfaceMacros(template, capability, moduleJson = {}, te let interfaceShape = getTemplate('/codeblocks/interface', templates) interfaceShape = interfaceShape.replace(/\$\{name\}/g, name) - .replace(/\$\{capability\}/g, capability) - .replace(/[ \t]*\$\{methods\}[ \t]*\n/g, iface.map(method => `\t${types.getMethodSignature(method, moduleJson, { destination: state.destination, isInterface: true })}`).join('\n') + '\n') + .replace(/\$\{capability\}/g, capability) + .replace(/[ \t]*\$\{methods\}[ \t]*\n/g, iface.map(method => `\t${types.getMethodSignature(method, moduleJson, { destination: state.destination, section: state.section, isInterface: true })}`).join('\n') + '\n') if (iface.length === 0) { - template = template.replace(/\$\{provider\.methods\}/gms, '') + template = template.replace(/\$\{provider\.methods\}/gms, '') } else { - let regex = /\$\{provider\.methods\}/gms - let match = template.match(regex) + let regex = /\$\{provider\.methods\}/gms + let match = template.match(regex) - let methodsBlock = '' - - // insert the standard method templates for each provider - if (match) { - iface.forEach(method => { - // add a tag to pick the correct template - method.tags.unshift({ - name: 'provider' - }) - const parametersSchema = method.params[0].schema - const parametersShape = types.getSchemaShape(parametersSchema, moduleJson, { destination: state.destination }) - let methodBlock = insertMethodMacros(getTemplateForMethod(method, templates), method, moduleJson, templates) - methodBlock = methodBlock.replace(/\${parameters\.shape\}/g, parametersShape) - const hasProviderParameters = parametersSchema && parametersSchema.properties && Object.keys(parametersSchema.properties).length > 0 - if (hasProviderParameters) { - const lines = methodBlock.split('\n') - for (let i = lines.length - 1; i >= 0; i--) { - if (lines[i].match(/\$\{provider\.param\.[a-zA-Z]+\}/)) { - let line = lines[i] - lines.splice(i, 1) - line = insertProviderParameterMacros(line, method.params[0].schema, moduleJson) - lines.splice(i++, 0, line) - } - } - methodBlock = lines.join('\n') - } - else { - methodBlock = methodBlock.replace(/\$\{if\.provider\.params\}.*?\$\{end\.if\.provider\.params\}/gms, '') - } - methodsBlock += methodBlock - }) - - match = template.match(regex) - template = template.replace(regex, methodsBlock) - } + let methodsBlock = '' - regex = /\$\{provider\.interface\.start\}.*?\$\{provider\.interface\.end\}/s - - // insert the granular method details for any ${provider.method.start} loops - while (match = template.match(regex)) { - let methodsBlock = '' - - const indent = (str, padding) => { - let first = true - return str.split('\n').map(line => { - if (first) { - first = false - return line - } - else { - return padding + line - } - }).join('\n') + // insert the standard method templates for each provider + if (match) { + iface.forEach(method => { + // add a tag to pick the correct template + method.tags.unshift({ + name: 'provider' + }) + const parametersSchema = method.params[0].schema + const parametersShape = types.getSchemaShape(parametersSchema, moduleJson, { destination: state.destination, section: state.section }) + let methodBlock = insertMethodMacros(getTemplateForMethod(method, templates), method, moduleJson, templates) + methodBlock = methodBlock.replace(/\${parameters\.shape\}/g, parametersShape) + const hasProviderParameters = parametersSchema && parametersSchema.properties && Object.keys(parametersSchema.properties).length > 0 + if (hasProviderParameters) { + const lines = methodBlock.split('\n') + for (let i = lines.length - 1; i >= 0; i--) { + if (lines[i].match(/\$\{provider\.param\.[a-zA-Z]+\}/)) { + let line = lines[i] + lines.splice(i, 1) + line = insertProviderParameterMacros(line, method.params[0].schema, moduleJson) + lines.splice(i++, 0, line) + } } + methodBlock = lines.join('\n') + } + else { + methodBlock = methodBlock.replace(/\$\{if\.provider\.params\}.*?\$\{end\.if\.provider\.params\}/gms, '') + } + methodsBlock += methodBlock + }) + + match = template.match(regex) + template = template.replace(regex, methodsBlock) + } - let i = 1 - iface.forEach(method => { + regex = /\$\{provider\.interface\.start\}.*?\$\{provider\.interface\.end\}/s - methodsBlock += match[0].replace(/\$\{provider\.interface\.name\}/g, method.name) - .replace(/\$\{provider\.interface\.Name\}/g, method.name.charAt(0).toUpperCase() + method.name.substr(1)) + // insert the granular method details for any ${provider.method.start} loops + while (match = template.match(regex)) { + let methodsBlock = '' - // first check for indented lines, and do the fancy indented replacement - .replace(/^([ \t]+)(.*?)\$\{provider\.interface\.example\.result\}/gm, '$1$2' + indent(JSON.stringify(method.examples[0].result.value, null, ' '), '$1')) - .replace(/^([ \t]+)(.*?)\$\{provider\.interface\.example\.parameters\}/gm, '$1$2' + indent(JSON.stringify(method.examples[0].params[0].value, null, ' '), '$1')) - // okay now just do the basic replacement (a single regex for both was not fun) - .replace(/\$\{provider\.interface\.example\.result\}/g, JSON.stringify(method.examples[0].result.value)) - .replace(/\$\{provider\.interface\.example\.parameters\}/g, JSON.stringify(method.examples[0].params[0].value)) + let i = 1 + iface.forEach(method => { - .replace(/\$\{provider\.interface\.example\.correlationId\}/g, JSON.stringify(method.examples[0].params[1].value.correlationId)) + methodsBlock += match[0].replace(/\$\{provider\.interface\.name\}/g, method.name) + .replace(/\$\{provider\.interface\.Name\}/g, method.name.charAt(0).toUpperCase() + method.name.substr(1)) - // a set of up to three RPC "id" values for generating intersting examples with matching ids - .replace(/\$\{provider\.interface\.i\}/g, i) - .replace(/\$\{provider\.interface\.j\}/g, (i+iface.length)) - .replace(/\$\{provider\.interface\.k\}/g, (i+2*iface.length)) + // first check for indented lines, and do the fancy indented replacement + .replace(/^([ \t]+)(.*?)\$\{provider\.interface\.example\.result\}/gm, '$1$2' + indent(JSON.stringify(method.examples[0].result.value, null, ' '), '$1')) + .replace(/^([ \t]+)(.*?)\$\{provider\.interface\.example\.parameters\}/gm, '$1$2' + indent(JSON.stringify(method.examples[0].params[0].value, null, ' '), '$1')) + // okay now just do the basic replacement (a single regex for both was not fun) + .replace(/\$\{provider\.interface\.example\.result\}/g, JSON.stringify(method.examples[0].result.value)) + .replace(/\$\{provider\.interface\.example\.parameters\}/g, JSON.stringify(method.examples[0].params[0].value)) - i++ - }) - methodsBlock = methodsBlock.replace(/\$\{provider\.interface\.[a-zA-Z]+\}/g, '') - template = template.replace(regex, methodsBlock) - } + .replace(/\$\{provider\.interface\.example\.correlationId\}/g, JSON.stringify(method.examples[0].params[1].value.correlationId)) + + // a set of up to three RPC "id" values for generating intersting examples with matching ids + .replace(/\$\{provider\.interface\.i\}/g, i) + .replace(/\$\{provider\.interface\.j\}/g, (i + iface.length)) + .replace(/\$\{provider\.interface\.k\}/g, (i + 2 * iface.length)) + + i++ + }) + methodsBlock = methodsBlock.replace(/\$\{provider\.interface\.[a-zA-Z]+\}/g, '') + template = template.replace(regex, methodsBlock) + } } // TODO: JSON-RPC examples need to use ${provider.interface} macros, but we're replacing them globally instead of each block @@ -1322,25 +1569,25 @@ function insertProviderInterfaceMacros(template, capability, moduleJson = {}, te function insertProviderParameterMacros(data = '', parameters, module = {}, options = {}) { if (!parameters || !parameters.properties) { - return '' + return '' } let result = '' Object.entries(parameters.properties).forEach(([name, param]) => { - let constraints = getSchemaConstraints(param, module) - let type = types.getSchemaType(param, module, { destination: state.destination, code: true, link: true, title: true, asPath: options.asPath, baseUrl: options.baseUrl }) + let constraints = getSchemaConstraints(param, module) + let type = types.getSchemaType(param, module, { destination: state.destination, section: state.section, code: true, link: true, title: true, asPath: options.asPath, baseUrl: options.baseUrl }) - if (constraints && type) { - constraints = '
' + constraints - } + if (constraints && type) { + constraints = '
' + constraints + } - result += data - .replace(/\$\{provider.param.name\}/, name) - .replace(/\$\{provider.param.summary\}/, param.description || '') - .replace(/\$\{provider.param.required\}/, (parameters.required && parameters.required.includes(name)) || 'false') - .replace(/\$\{provider.param.type\}/, type) - .replace(/\$\{provider.param.constraints\}/, constraints) + '\n' + result += data + .replace(/\$\{provider.param.name\}/, name) + .replace(/\$\{provider.param.summary\}/, param.description || '') + .replace(/\$\{provider.param.required\}/, (parameters.required && parameters.required.includes(name)) || 'false') + .replace(/\$\{provider.param.type\}/, type) + .replace(/\$\{provider.param.constraints\}/, constraints) + '\n' }) return result @@ -1350,7 +1597,7 @@ export { generateMacros, insertMacros, generateAggregateMacros, - insertAggregateMacros, + insertAggregateMacros } export default { @@ -1360,4 +1607,4 @@ export default { insertAggregateMacros, setTyper, setConfig -} \ No newline at end of file +} diff --git a/src/macrofier/index.mjs b/src/macrofier/index.mjs index 89c194e2..ce8d3d8d 100644 --- a/src/macrofier/index.mjs +++ b/src/macrofier/index.mjs @@ -18,7 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { emptyDir, readDir, readFiles, readJson, writeFiles, writeText } from '../shared/filesystem.mjs' +import { emptyDir, readDir, readFiles, readFilesPermissions, readJson, + writeFiles, writeFilesPermissions, writeText } from '../shared/filesystem.mjs' import { getTemplate, getTemplateForModule } from '../shared/template.mjs' import { getModule, hasPublicAPIs } from '../shared/modules.mjs' import { logHeader, logSuccess } from '../shared/io.mjs' @@ -42,8 +43,12 @@ const macrofy = async ( staticContent, templatesPerModule, templatesPerSchema, + persistPermission, + createPolymorphicMethods, createModuleDirectories, copySchemasIntoModules, + extractSubSchemas, + excludeDeclarations, aggregateFile, operators, hidePrivate = true, @@ -61,8 +66,6 @@ const macrofy = async ( return new Promise( async (resolve, reject) => { const openrpc = await readJson(input) - - logHeader(`Generating ${headline} for version ${openrpc.info.title} ${openrpc.info.version}`) let typer @@ -79,6 +82,8 @@ const macrofy = async ( engine.setConfig({ copySchemasIntoModules, createModuleDirectories, + extractSubSchemas, + excludeDeclarations, operators }) @@ -87,6 +92,12 @@ const macrofy = async ( const sharedTemplateList = await readDir(sharedTemplates, { recursive: true }) const templates = Object.assign(await readFiles(sharedTemplateList, sharedTemplates), await readFiles(sdkTemplateList, template)) // sdkTemplates are second so they win ties + let templatesPermission = {} + if (persistPermission) { + templatesPermission = Object.assign(await readFilesPermissions(sharedTemplateList, sharedTemplates), + await readFilesPermissions(sdkTemplateList, template)) + } + const exampleTemplates = {} for (var i=0; i { - const macros = engine.generateMacros(module, templates, exampleTemplates, {hideExcluded: hideExcluded, copySchemasIntoModules: copySchemasIntoModules, destination: t}) + const macros = engine.generateMacros(module, templates, exampleTemplates, {hideExcluded: hideExcluded, copySchemasIntoModules: copySchemasIntoModules, createPolymorphicMethods: createPolymorphicMethods, destination: t}) let content = getTemplateForModule(module.info.title, 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 @@ -154,14 +170,14 @@ const macrofy = async ( content = engine.insertMacros(content, macros) content = engine.insertAggregateMacros(content, aggregateMacros) - const location = createModuleDirectories ? path.join(output, module.info.title, t) : path.join(output, t.replace(/Module/, module.info.title).replace(/index/, module.info.title)) + const location = createModuleDirectories ? path.join(output, module.info.title, t) : path.join(output, t.replace(/module/, module.info.title.toLowerCase()).replace(/index/, module.info.title)) outputFiles[location] = content logSuccess(`Generated macros for module ${path.relative(output, location)}`) }) if (primaryOutput) { - const macros = engine.generateMacros(module, templates, exampleTemplates, {hideExcluded: hideExcluded, copySchemasIntoModules: copySchemasIntoModules, destination: primaryOutput}) + const macros = engine.generateMacros(module, templates, exampleTemplates, {hideExcluded: hideExcluded, copySchemasIntoModules: copySchemasIntoModules, createPolymorphicMethods: createPolymorphicMethods, destination: primaryOutput}) macros.append = append outputFiles[primaryOutput] = engine.insertMacros(outputFiles[primaryOutput], macros) } @@ -199,8 +215,6 @@ const macrofy = async ( delete outputFiles[file] } }) - - console.log() } // Grab all schema groups w/ a URI string. These came from some external json-schema that was bundled into the OpenRPC @@ -245,13 +259,13 @@ const macrofy = async ( Object.values(externalSchemas).forEach( document => { if (templatesPerSchema) { templatesPerSchema.forEach( t => { - const macros = engine.generateMacros(document, templates, exampleTemplates, {hideExcluded: hideExcluded, destination: 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).replace(/index/, document.info.title)) + 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)) outputFiles[location] = content logSuccess(`Generated macros for schema ${path.relative(output, location)}`) @@ -264,11 +278,14 @@ const macrofy = async ( await emptyDir(output) } - await writeFiles(outputFiles) + await writeFiles(outputFiles) + if (persistPermission) { + await writeFilesPermissions(templatesPermission) + } logSuccess(`Wrote ${Object.keys(outputFiles).length} files.`) resolve() }) } -export default macrofy \ No newline at end of file +export default macrofy diff --git a/src/sdk/index.mjs b/src/sdk/index.mjs index 9cfd547f..5d7effa2 100755 --- a/src/sdk/index.mjs +++ b/src/sdk/index.mjs @@ -57,9 +57,13 @@ const run = async ({ staticContent: path.join(language, 'src', 'shared'), templatesPerModule: config.templatesPerModule, templatesPerSchema: config.templatesPerSchema, + persistPermission: config.persistPermission, + createPolymorphicMethods: config.createPolymorphicMethods, operators: config.operators, createModuleDirectories: config.createModuleDirectories, copySchemasIntoModules: config.copySchemasIntoModules, + extractSubSchemas: config.extractSubSchemas, + excludeDeclarations: config.excludeDeclarations, staticModuleNames: staticModuleNames, hideExcluded: true, aggregateFile: config.aggregateFile, @@ -70,4 +74,4 @@ const run = async ({ }) } -export default run \ No newline at end of file +export default run diff --git a/src/shared/filesystem.mjs b/src/shared/filesystem.mjs index 46e2db9a..2c48e139 100644 --- a/src/shared/filesystem.mjs +++ b/src/shared/filesystem.mjs @@ -1,9 +1,11 @@ import path from 'path' -import { readFile, writeFile, readdir } from 'fs/promises' -import { lstatSync } from 'fs' +import { readFile, writeFile, readdir, stat } from 'fs/promises' +import { lstatSync, chmodSync } from 'fs' import { emptyDir } from 'fs-extra' import { mkdirpSync as mkdirSync } from 'fs-extra' +const setPermission = (ref, mode) => chmodSync(ref, mode) + const readText = ref => readFile(ref) .then(resp => resp.toString()) @@ -39,6 +41,34 @@ const readDir = async (ref, options) => { return results.sort() } +const getIndex = (refs, base) => { + let index = base ? base.length : 0 + if (base && !refs[0].startsWith(base)) { + refs = refs.map(v => path.relative(base, v)) + index = 0 + } + else if (index === 0 && refs.length !== 1) { + // find the common prefix of all the files + while ((new Set(refs.map(r => r[index]))).size === 1) { + index++ + } + // back up one dirctory from the common prefix + index = path.join(path.join(refs[0].substring(0, index)), '..').length + } + return index +} + +const readFilesPermissions = (refs, base) => Promise.all(refs.map(ref => stat(ref))) + .then(permissions => { + if (!refs || refs.length === 0) { + return Promise.resolve({}) + } + const results = refs.map(v => [v.substring(getIndex(refs, base)), null]) + for (let i=0; i Promise.all(refs.map(ref => readFile(ref))) if (!refs || refs.length === 0) { return Promise.resolve({}) } - - let index = base ? base.length : 0 - if (base && !refs[0].startsWith(base)) { - refs = refs.map(v => path.relative(base, v)) - index = 0 - } - else if (index === 0 && refs.length !== 1) { - // find the common prefix of all the files - while ((new Set(refs.map(r => r[index]))).size === 1) { - index++ - } - - // back up one dirctory from the common prefix - index = path.join(path.join(refs[0].substring(0, index)), '..').length - } - - const results = refs.map(v => [v.substring(index), null]) + const results = refs.map(v => [v.substring(getIndex(refs, base)), null]) for (let i=0; i refs[i].endsWith(suffix))) { results[i][1] = contents[i] @@ -83,6 +97,11 @@ const writeFiles = (files) => { .map( ([file, contents]) => writeText(file, contents))) } +const writeFilesPermissions = (files) => { + return Promise.all(Object.entries(files) + .map( ([file, mode]) => setPermission(file, mode))) +} + export { readText, writeText, @@ -91,5 +110,7 @@ export { readDir, readFiles, writeFiles, - emptyDir -} \ No newline at end of file + emptyDir, + readFilesPermissions, + writeFilesPermissions +} diff --git a/src/shared/modules.mjs b/src/shared/modules.mjs index 386b9d6d..927092c2 100644 --- a/src/shared/modules.mjs +++ b/src/shared/modules.mjs @@ -29,6 +29,7 @@ const { and, not } = logic import isString from 'crocks/core/isString.js' import predicates from 'crocks/predicates/index.js' import { getExternalSchemaPaths, isDefinitionReferencedBySchema, isNull, localizeDependencies, isSchema, getLocalSchemaPaths, replaceRef } from './json-schema.mjs' +import { getPath as getRefDefinition } from './json-schema.mjs' const { isObject, isArray, propEq, pathSatisfies, hasProp, propSatisfies } = predicates // util for visually debugging crocks ADTs @@ -227,6 +228,13 @@ const isTemporalSetMethod = compose( getPath(['tags']) ) +const isCallsMetricsMethod = compose( + option(false), + map(_ => true), + chain(find(propEq('name', 'calls-metrics'))), + getPath(['tags']) +) + const getMethodAttributes = compose( option(null), map(props => props.reduce( (val, item) => { @@ -854,6 +862,137 @@ const generateEventListenResponse = json => { return json } +const getAnyOfSchema = (inType, json) => { + let anyOfTypes = [] + let outType = localizeDependencies(inType, json) + if (outType.schema.anyOf) { + let definition = '' + if (inType.schema['$ref'] && (inType.schema['$ref'][0] === '#')) { + definition = getRefDefinition(inType.schema['$ref'], json, json['x-schemas']) + } + else { + definition = outType.schema + } + definition.anyOf.forEach(anyOf => { + anyOfTypes.push(anyOf) + }) + outType.schema.anyOf = anyOfTypes + } + return outType +} + +const generateAnyOfSchema = (anyOf, name, summary) => { + let anyOfType = {} + anyOfType["name"] = name[0].toLowerCase() + name.substr(1) + anyOfType["summary"] = summary + anyOfType["schema"] = anyOf + return anyOfType +} + +const generateParamsAnyOfSchema = (methodParams, anyOf, anyOfTypes, title, summary) => { + let params = [] + methodParams.forEach(p => { + if (p.schema.anyOf === anyOfTypes) { + let anyOfType = generateAnyOfSchema(anyOf, title, summary) + anyOfType.required = p.required + params.push(anyOfType) + } + else { + params.push(p) + } + }) + return params +} + +const generateResultAnyOfSchema = (method, methodResult, anyOf, anyOfTypes, title, summary) => { + let methodResultSchema = {} + if (methodResult.schema.anyOf === anyOfTypes) { + let anyOfType = generateAnyOfSchema(anyOf, title, summary) + let index = 0 + if (isEventMethod(method)) { + index = (method.result.schema.anyOf || method.result.schema.oneOf).indexOf(getPayloadFromEvent(method)) + } + else { + index = (method.result.schema.anyOf || method.result.schema.oneOf).indexOf(anyOfType) + } + if (method.result.schema.anyOf) { + methodResultSchema["anyOf"] = Object.assign([], method.result.schema.anyOf) + methodResultSchema.anyOf[index] = anyOfType.schema + } + else if (method.result.schema.oneOf) { + methodResultSchema["oneOf"] = Object.assign([], method.result.schema.oneOf) + methodResultSchema.oneOf[index] = anyOfType.schema + } + else { + methodResultSchema = anyOfType.schema + } + } + return methodResultSchema +} + +const createPolymorphicMethods = (method, json) => { + let anyOfTypes + let methodParams = [] + let methodResult = Object.assign({}, method.result) + method.params.forEach(p => { + if (p.schema) { + let param = getAnyOfSchema(p, json) + if (param.schema.anyOf && anyOfTypes) { + //anyOf is allowed with only one param in the params list + throw `WARNING anyOf is repeated with param:${p}` + } + else if (param.schema.anyOf) { + anyOfTypes = param.schema.anyOf + } + methodParams.push(param) + } + }) + let foundAnyOfParams = anyOfTypes ? true : false + + if (isEventMethod(method)) { + methodResult.schema = getPayloadFromEvent(method) + } + methodResult = getAnyOfSchema(methodResult, json) + let foundAnyOfResult = methodResult.schema.anyOf ? true : false + if (foundAnyOfParams === true && foundAnyOfResult === true) { + throw `WARNING anyOf is already with param schema, it is repeated with ${method.name} result too` + } + else if (foundAnyOfResult === true) { + anyOfTypes = methodResult.schema.anyOf + } + let polymorphicMethodSchemas = [] + //anyOfTypes will be allowed either in any one of the params or in result + if (anyOfTypes) { + let polymorphicMethodSchema = { + name: {}, + tags: {}, + summary: `${method.summary}`, + params: {}, + result: {}, + examples: {} + } + anyOfTypes.forEach(anyOf => { + + let localized = localizeDependencies(anyOf, json) + let title = localized.title || localized.name || '' + let summary = localized.summary || localized.description || '' + polymorphicMethodSchema.title = method.name + polymorphicMethodSchema.name = foundAnyOfParams ? `${method.name}With${title}` : `${method.name}${title}` + polymorphicMethodSchema.tags = method.tags + polymorphicMethodSchema.params = foundAnyOfParams ? generateParamsAnyOfSchema(methodParams, anyOf, anyOfTypes, title, summary) : methodParams + polymorphicMethodSchema.result = Object.assign({}, method.result) + polymorphicMethodSchema.result.schema = foundAnyOfResult ? generateResultAnyOfSchema(method, methodResult, anyOf, anyOfTypes, title, summary) : methodResult + polymorphicMethodSchema.examples = method.examples + polymorphicMethodSchemas.push(Object.assign({}, polymorphicMethodSchema)) + }) + } + else { + polymorphicMethodSchemas = method + } + + return polymorphicMethodSchemas +} + const getPathFromModule = (module, path) => { console.error("DEPRECATED: getPathFromModule") @@ -1148,6 +1287,7 @@ export { isPolymorphicReducer, isPolymorphicPullMethod, isTemporalSetMethod, + isCallsMetricsMethod, isExcludedMethod, isRPCOnlyMethod, isProviderInterfaceMethod, @@ -1175,5 +1315,6 @@ export { getSemanticVersion, addExternalMarkdown, addExternalSchemas, - getExternalMarkdownPaths -} \ No newline at end of file + getExternalMarkdownPaths, + createPolymorphicMethods +} diff --git a/src/shared/typescript.mjs b/src/shared/typescript.mjs index aa6e21f5..5982137e 100644 --- a/src/shared/typescript.mjs +++ b/src/shared/typescript.mjs @@ -199,10 +199,6 @@ function getSchemaShape(schema = {}, module = {}, { name = '', level = 0, title, } function getSchemaType(schema, module, { destination, link = false, title = false, code = false, asPath = false, event = false, expandEnums = true, baseUrl = '' } = {}) { - if (schema.schema) { - schema = schema.schema - } - const wrap = (str, wrapper) => wrapper + str + wrapper if (schema['$ref']) { @@ -327,7 +323,11 @@ function getSchemaShape(schema = {}, module = {}, { name = '', level = 0, title, return wrap('void', code ? '`' : '') } } - + + function getJsonType(schema, module, { destination, link = false, title = false, code = false, asPath = false, event = false, expandEnums = true, baseUrl = '' } = {}) { + return '' + } + function getTypeScriptType(jsonType) { if (jsonType === 'integer') { return 'number' @@ -346,9 +346,14 @@ function getSchemaShape(schema = {}, module = {}, { name = '', level = 0, title, return acc } + function getSchemaInstantiation(schema, module, { instantiationType }) { + return '' + } export default { getMethodSignature, getMethodSignatureParams, getSchemaShape, - getSchemaType + getSchemaType, + getJsonType, + getSchemaInstantiation } \ No newline at end of file