Skip to content

Commit

Permalink
fix: Build issues with CPP Manage SDK (#203)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ksentak committed Aug 8, 2024
1 parent 0c00896 commit e06f6e2
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
{
${properties}
}
${base.title}Result${property.dependency}${if.impl.optional}.value()${end.if.impl.optional}.${property}.push_back(${property}Result${level});
${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});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface ${name} {
${methods}
}
203 changes: 115 additions & 88 deletions src/shared/json-schema.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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<refs.length; i++) {
let path = refs[i]
const ref = getPathOr(null, path, definition)
path.pop() // drop ref
if (refToPath(ref).length > 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<refs.length; i++) {
let path = refs[i]
const ref = getPathOr(null, path, definition)

path.pop() // drop ref
let resolvedSchema

if (!resolvedSchema) {
resolvedSchema = { "$REF": ref}
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

if (options.keepRefsAndLocalizeAsComponent) {
// if copying schemas, just drop them in components.schemas
if (keepRefsAndLocalizeAsComponent) {
const title = ref.split('/').pop()
definition.components = definition.components || {}
definition.components.schemas = definition.components.schemas || {}
definition.components.schemas[title] = resolvedSchema
definition = setPath([...path, '$ref'], `#/components/schemas/${title}`, definition)
}
else {
// otherwise, copy the schema definition to the exact location of the old $ref
} else {
definition = setPath(path, resolvedSchema, definition)
}
}
else {
// TODO: do we need keepRefsAndLocalizeAsComponent support at the root? i don't think so, that would mean that an OpenRPC doc just pointed to another right from the root.
} else {
delete definition['$ref']
Object.assign(definition, resolvedSchema)
}
}
refs = getExternalSchemaPaths(definition)
refs = externalOnly ? getExternalSchemaPaths(definition) : getLocalSchemaPaths(definition)
}
return definition
}

const findAndMergeAllOfs = (pointer) => {
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)
}

Expand Down Expand Up @@ -519,5 +545,6 @@ export {
replaceRef,
removeIgnoredAdditionalItems,
mergeAnyOf,
mergeOneOf
mergeOneOf,
dereferenceAndMergeAllOfs
}
8 changes: 4 additions & 4 deletions src/shared/modules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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'])
Expand Down Expand Up @@ -157,7 +157,7 @@ function getProviderInterface(capability, module, extractProviderSchema = false)
method.tags = method.tags.filter(tag => tag.name !== 'event')
}
})

return iface
}

Expand Down

0 comments on commit e06f6e2

Please sign in to comment.