Skip to content

Commit

Permalink
Merge pull request temando#81 from brendo/issue-5-one-of-parser
Browse files Browse the repository at this point in the history
Allow oneOf to be parsed. RE: temando#5
  • Loading branch information
quangkhoa committed Jun 6, 2017
2 parents 1e626aa + 3c14c70 commit 3cb4c50
Show file tree
Hide file tree
Showing 21 changed files with 692 additions and 195 deletions.
145 changes: 145 additions & 0 deletions src/parser/open-api/oneOfResolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import cloneDeep from 'lodash/cloneDeep'
import get from 'lodash/get'
import update from 'lodash/update'
import toPath from 'lodash/toPath'

/**
* Recursively determine if the given object has any `oneOf` properties.
* Returns an array with the paths to those properties (eg. `properties.oneOf`).
* If no `oneOf` keys were found, an empty array is returned.
*
* @param {object} obj
* @return {string[]}
*/
function getOneOfPaths (obj) {
let paths = []
let walk = function (obj, path = '') {
for (let key in obj) {
// Handle if `oneOf` is found at the first level.
const currentPath = (path === '') ? key : `${path}.${key}`

if (key === 'oneOf') {
paths.push(currentPath)
} else if (typeof obj[key] === 'object' || Array.isArray(obj[key])) {
walk(obj[key], currentPath)
}
}
}

walk(obj)

return paths
}

/**
* Returns all of the possible states of `obj` when the permutations
* specified in `paths` are applied.
*
* @param {string[]} paths
* @param {object} obj
* @return {object[]}
*/
function getStates (paths, obj) {
let states = [ ...getStateAt(paths[0], obj) ]

for (let i = 1; i < paths.length; i++) {
let state = states.shift()

while (state) {
const newStates = getStateAt(paths[i], state)

// if there are no new states, put this one back and stop
if (!newStates.length) {
states.unshift(state)
break
}

states = states.concat(...newStates)
state = states.shift()
}
}

return states
}

/**
* Given a path and an object, return all the new states that exist
* by merging the options found at `obj.path` into the given object.
* Returns an array of possible objects
*
* @param {string} path
* @param {object} obj
* @return {object[]}
*/
function getStateAt (path, obj) {
const clonedObj = cloneDeep(obj)
const states = get(clonedObj, path)

// Couldn't retrieve the states at this path, bail.
if (states === undefined) {
return []
}

const parentPath = getParentPath(path)
return states.map((state) => {
// No path, so add state to object directly
if (parentPath === '') {
return merge(clonedObj, state)
}

// Replace the path with the state
update(clonedObj, parentPath, (value) => merge(value, state))

return cloneDeep(clonedObj)
})
}

/**
* Given a path, drop the last segment and return the shortened
* path, which will be the parent of the given path.
*
* @param {string} path
* @return {string}
*/
function getParentPath (path) {
let rootPath = toPath(path)
rootPath.pop()

return rootPath.join('.')
}

/**
* Take a object and merge the state to it, returning the resulting object.
* Will remove the `oneOf` key from the given `obj`.
*
* @param {object} obj
* @param {object} state
* @return {object}
*/
function merge (obj, state) {
delete obj.oneOf

return { ...obj, ...state }
}

/**
* Resolves all `oneOf` definitions present in the given object.
* If none exist, the object is returned untouched. Otherwise
* an object with a `oneOf` property whose value is an array of
* states is returned
*
* @param {object} obj
* @return {object}
*/
export function resolveOneOf (obj) {
const paths = getOneOfPaths(obj)
if (paths.length === 0) {
return obj
}

const states = getStates(paths, obj)

return {
oneOf: states
}
}
23 changes: 14 additions & 9 deletions src/parser/open-api/schemaParser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { resolveAllOf } from './allOfResolver'
import { resolveOneOf } from './oneOfResolver'
import { hasConstraints, getConstraints } from './constraints/constraintsParser'

const literalTypes = ['string', 'integer', 'number', 'boolean']
Expand Down Expand Up @@ -92,13 +93,11 @@ function getPropertiesNode (propertiesNode, requiredProperties = []) {
const outputNode = []

for (const key in propertiesNode) {
if (propertiesNode.hasOwnProperty(key)) {
const property = propertiesNode[key]
const value = getPropertyNode(key, property, requiredProperties.includes(key))
const property = propertiesNode[key]
const value = getPropertyNode(key, property, requiredProperties.includes(key))

if (value) {
outputNode.push(value)
}
if (value) {
outputNode.push(value)
}
}

Expand All @@ -113,8 +112,14 @@ function getPropertiesNode (propertiesNode, requiredProperties = []) {
* @return {Object}
*/
export default function getUIReadySchema (jsonSchema) {
const resolvedJsonSchema = resolveAllOf(jsonSchema)
const outputSchema = getPropertiesNode(resolvedJsonSchema.properties, resolvedJsonSchema.required)
let resolved = resolveAllOf(jsonSchema)
resolved = resolveOneOf(resolved)

if (Array.isArray(resolved.oneOf)) {
return resolved.oneOf.map(
(state) => getPropertiesNode(state.properties, state.required)
)
}

return outputSchema
return getPropertiesNode(resolved.properties, resolved.required)
}
Loading

0 comments on commit 3cb4c50

Please sign in to comment.