From e06f6e2b8c103d44c177351eaa05f9d965b61518 Mon Sep 17 00:00:00 2001 From: Keaton Sentak <54916859+ksentak@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:08:12 -0400 Subject: [PATCH] fix: Build issues with CPP Manage SDK (#203) * fix: Add function to only dereferenceAndMergeAllOfs * fix: Small template update * fix: Add focusable interface template for js * fix: Combine common functionality for cleaner code * fix: Adjust mergeAllOfs functionality --- .../sub-property/object-array.cpp | 2 +- .../codeblocks/interface-focusable.mjs | 3 + src/shared/json-schema.mjs | 203 ++++++++++-------- src/shared/modules.mjs | 8 +- 4 files changed, 123 insertions(+), 93 deletions(-) create mode 100644 languages/javascript/templates/codeblocks/interface-focusable.mjs diff --git a/languages/cpp/templates/result-instantiation/sub-property/object-array.cpp b/languages/cpp/templates/result-instantiation/sub-property/object-array.cpp index c7286760..5a99765d 100644 --- a/languages/cpp/templates/result-instantiation/sub-property/object-array.cpp +++ b/languages/cpp/templates/result-instantiation/sub-property/object-array.cpp @@ -3,4 +3,4 @@ { ${properties} } - ${base.title}Result${property.dependency}${if.impl.optional}.value()${end.if.impl.optional}.${property}.push_back(${property}Result${level}); \ No newline at end of file + ${base.title}Result${property.dependency}${if.impl.optional}.value()${end.if.impl.optional}.${property}${if.optional}.value()${end.if.optional}.push_back(${property}Result${level}); \ No newline at end of file diff --git a/languages/javascript/templates/codeblocks/interface-focusable.mjs b/languages/javascript/templates/codeblocks/interface-focusable.mjs new file mode 100644 index 00000000..8d1fcfbc --- /dev/null +++ b/languages/javascript/templates/codeblocks/interface-focusable.mjs @@ -0,0 +1,3 @@ +interface ${name} { + ${methods} +} diff --git a/src/shared/json-schema.mjs b/src/shared/json-schema.mjs index 98fdeaba..f140cfd2 100644 --- a/src/shared/json-schema.mjs +++ b/src/shared/json-schema.mjs @@ -282,126 +282,152 @@ const schemaReferencesItself = (schema, path) => { return false } -// TODO: get rid of schemas param, after updating the validate task to use addExternalSchemas -const localizeDependencies = (json, document, schemas = {}, options = defaultLocalizeOptions) => { - if (typeof options === 'boolean') { - // if we got a boolean, then inject it into the default options for the externalOnly value (for backwards compatibility) - options = Object.assign(JSON.parse(JSON.stringify(defaultLocalizeOptions)), { externalOnly: options }) - } - - let definition = JSON.parse(JSON.stringify(json)) - let refs = getLocalSchemaPaths(definition) - let unresolvedRefs = [] +/** + * Deep clones an object to avoid mutating the original. + * @param {Object} obj - The object to clone. + * @returns {Object} - The cloned object. + */ +const cloneDeep = (obj) => JSON.parse(JSON.stringify(obj)) + +/** + * Dereferences schema paths and resolves references. + * @param {Array} refs - Array of schema paths to dereference. + * @param {Object} definition - The schema definition. + * @param {Object} document - The document containing schemas. + * @param {Array} unresolvedRefs - Array to collect unresolved references. + * @param {boolean} [externalOnly=false] - Whether to only dereference external schemas. + * @param {boolean} [keepRefsAndLocalizeAsComponent=false] - Whether to keep references and localize as components. + * @returns {Object} - The updated schema definition. + */ +const dereferenceSchema = (refs, definition, document, unresolvedRefs, externalOnly = false, keepRefsAndLocalizeAsComponent = false) => { + while (refs.length > 0) { + for (let i = 0; i < refs.length; i++) { + let path = refs[i] + const ref = getPathOr(null, path, definition) + path.pop() // drop ref - if (!options.externalOnly) { - while (refs.length > 0) { - for (let i=0; i 1) { - let resolvedSchema = JSON.parse(JSON.stringify(getPathOr(null, refToPath(ref), document))) - if (schemaReferencesItself(resolvedSchema, refToPath(ref))) { - resolvedSchema = null - } + let resolvedSchema = cloneDeep(getPathOr(null, refToPath(ref), document)) - if (!resolvedSchema) { - resolvedSchema = { "$REF": ref} - unresolvedRefs.push([...path]) - } - - if (path.length) { - // don't loose examples from original object w/ $ref - // todo: should we preserve other things, like title? - const examples = getPathOr(null, [...path, 'examples'], definition) - resolvedSchema.examples = examples || resolvedSchema.examples - definition = setPath(path, resolvedSchema, definition) - } - else { - delete definition['$ref'] - Object.assign(definition, resolvedSchema) - } - } + if (schemaReferencesItself(resolvedSchema, refToPath(ref))) { + resolvedSchema = null } - refs = getLocalSchemaPaths(definition) - } - } - - refs = getExternalSchemaPaths(definition) - while (refs.length > 0) { - for (let i=0; i { + let allOfFound = false; + + const mergeAllOfs = (obj) => { + if (Array.isArray(obj) && obj.length === 0) { + return obj; + } + + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + if (key === 'allOf' && Array.isArray(obj[key])) { + const union = deepmerge.all(obj.allOf.reverse()); + const title = obj.title; + Object.assign(obj, union); + if (title) { + obj.title = title; + } + delete obj.allOf; + allOfFound = true; + } else if (typeof obj[key] === 'object') { + mergeAllOfs(obj[key]); + } + } + } + }; + + mergeAllOfs(pointer); + return allOfFound; +}; + +/** + * Dereferences and merges allOf entries in a method schema. + * @param {Object} method - The method schema to dereference and merge. + * @param {Object} module - The module containing schemas. + * @returns {Object} - The dereferenced and merged schema. + */ +const dereferenceAndMergeAllOfs = (method, module) => { + let definition = cloneDeep(method) + let unresolvedRefs = [] + const originalDefinition = cloneDeep(definition) + + definition = dereferenceSchema(getLocalSchemaPaths(definition), definition, module, unresolvedRefs) + definition = dereferenceSchema(getExternalSchemaPaths(definition), definition, module, unresolvedRefs, true) + + const allOfFound = findAndMergeAllOfs(definition) + + if (!allOfFound) { + return originalDefinition } - unresolvedRefs.forEach(ref => { + unresolvedRefs.forEach((ref) => { let node = getPathOr({}, ref, definition) node['$ref'] = node['$REF'] delete node['$REF'] }) - if (options.mergeAllOfs) { - const findAndMergeAllOfs = pointer => { - if ((typeof pointer) !== 'object' || !pointer) { - return - } + return definition +} + +/** + * Localizes dependencies in a JSON schema, dereferencing references and optionally merging allOf entries. + * @param {Object} json - The JSON schema to localize. + * @param {Object} document - The document containing schemas. + * @param {Object} [schemas={}] - Additional schemas to use for dereferencing. + * @param {Object|boolean} [options=defaultLocalizeOptions] - Options for localization, or a boolean for externalOnly. + * @returns {Object} - The localized schema. + */ +const localizeDependencies = (json, document, schemas = {}, options = defaultLocalizeOptions) => { + if (typeof options === 'boolean') { + options = { ...defaultLocalizeOptions, externalOnly: options } + } - Object.keys(pointer).forEach( key => { + let definition = cloneDeep(json) + let unresolvedRefs = [] - if (Array.isArray(pointer) && key === 'length') { - return - } - // do a depth-first search for `allOfs` to reduce complexity of merges - if ((key !== 'allOf') && (typeof pointer[key] === 'object')) { - findAndMergeAllOfs(pointer[key]) - } - else if (key === 'allOf' && Array.isArray(pointer[key])) { - const union = deepmerge.all(pointer.allOf.reverse()) // reversing so lower `title` attributes will win - const title = pointer.title - Object.assign(pointer, union) - if (title) { - pointer.title = title - } - delete pointer.allOf - } - }) - } + if (!options.externalOnly) { + definition = dereferenceSchema(getLocalSchemaPaths(definition), definition, document, unresolvedRefs) + } + + definition = dereferenceSchema(getExternalSchemaPaths(definition), definition, document, unresolvedRefs, true, options.keepRefsAndLocalizeAsComponent) + unresolvedRefs.forEach((ref) => { + let node = getPathOr({}, ref, definition) + node['$ref'] = node['$REF'] + delete node['$REF'] + }) + + if (options.mergeAllOfs) { findAndMergeAllOfs(definition) } @@ -519,5 +545,6 @@ export { replaceRef, removeIgnoredAdditionalItems, mergeAnyOf, - mergeOneOf + mergeOneOf, + dereferenceAndMergeAllOfs } diff --git a/src/shared/modules.mjs b/src/shared/modules.mjs index ae7dca1f..097bd7c5 100644 --- a/src/shared/modules.mjs +++ b/src/shared/modules.mjs @@ -28,7 +28,7 @@ import isEmpty from 'crocks/core/isEmpty.js' 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, getPropertySchema } from './json-schema.mjs' +import { getExternalSchemaPaths, isDefinitionReferencedBySchema, isNull, localizeDependencies, isSchema, getLocalSchemaPaths, replaceRef, getPropertySchema, dereferenceAndMergeAllOfs } from './json-schema.mjs' import { getPath as getRefDefinition } from './json-schema.mjs' const { isObject, isArray, propEq, pathSatisfies, hasProp, propSatisfies } = predicates @@ -91,8 +91,8 @@ const getProviderInterfaceMethods = (capability, json) => { function getProviderInterface(capability, module, extractProviderSchema = false) { module = JSON.parse(JSON.stringify(module)) - const iface = getProviderInterfaceMethods(capability, module)//.map(method => localizeDependencies(method, module, null, { mergeAllOfs: true })) - + const iface = getProviderInterfaceMethods(capability, module).map(method => dereferenceAndMergeAllOfs(method, module)) + iface.forEach(method => { const payload = getPayloadFromEvent(method) const focusable = method.tags.find(t => t['x-allow-focus']) @@ -157,7 +157,7 @@ function getProviderInterface(capability, module, extractProviderSchema = false) method.tags = method.tags.filter(tag => tag.name !== 'event') } }) - + return iface }