155
script/rest/utils/get-body-params.js
Normal file
155
script/rest/utils/get-body-params.js
Normal file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env node
|
||||
import renderContent from '../../../lib/render-content/index.js'
|
||||
|
||||
// If there is a oneOf at the top level, then we have to present just one
|
||||
// in the docs. We don't currently have a convention for showing more than one
|
||||
// set of input parameters in the docs. Having a top-level oneOf is also very
|
||||
// uncommon.
|
||||
// Currently there aren't very many operations that require this treatment.
|
||||
// As an example, the 'Add status check contexts' and 'Set status check contexts'
|
||||
// operations have a top-level oneOf.
|
||||
|
||||
async function getTopLevelOneOfProperty(schema) {
|
||||
if (!schema.oneOf) {
|
||||
throw new Error('Schema does not have a requestBody oneOf property defined')
|
||||
}
|
||||
if (!(Array.isArray(schema.oneOf) && schema.oneOf.length > 0)) {
|
||||
throw new Error('Schema requestBody oneOf property is not an array')
|
||||
}
|
||||
// When a oneOf exists but the `type` differs, the case has historically
|
||||
// been that the alternate option is an array, where the first option
|
||||
// is the array as a property of the object. We need to ensure that the
|
||||
// first option listed is the most comprehensive and preferred option.
|
||||
const firstOneOfObject = schema.oneOf[0]
|
||||
const allOneOfAreObjects = schema.oneOf.every((elem) => elem.type === 'object')
|
||||
let required = firstOneOfObject.required || []
|
||||
let properties = firstOneOfObject.properties || {}
|
||||
|
||||
// When all of the oneOf objects have the `type: object` we
|
||||
// need to display all of the parameters.
|
||||
// This merges all of the properties and required values.
|
||||
if (allOneOfAreObjects) {
|
||||
for (const each of schema.oneOf.slice(1)) {
|
||||
Object.assign(firstOneOfObject.properties, each.properties)
|
||||
required = firstOneOfObject.required.concat(each.required)
|
||||
}
|
||||
properties = firstOneOfObject.properties
|
||||
}
|
||||
return { properties, required }
|
||||
}
|
||||
|
||||
// Gets the body parameters for a given schema recursively.
|
||||
export async function getBodyParams(schema, topLevel = false, summary = '', depth = 1) {
|
||||
if (summary && depth > 3) console.log(depth, summary)
|
||||
const bodyParametersParsed = []
|
||||
const schemaObject = schema.oneOf && topLevel ? await getTopLevelOneOfProperty(schema) : schema
|
||||
const properties = schemaObject.properties || {}
|
||||
const required = schemaObject.required || []
|
||||
|
||||
for (const [paramKey, param] of Object.entries(properties)) {
|
||||
const paramDecorated = {}
|
||||
|
||||
// OpenAPI 3.0 only had a single value for `type`. OpenAPI 3.1
|
||||
// will either be a single value or an array of values.
|
||||
// This makes type an array regardless of how many values the array
|
||||
// includes. This allows us to support 3.1 while remaining backwards
|
||||
// compatible with 3.0.
|
||||
const paramType = Array.isArray(param.type) ? param.type : [param.type]
|
||||
const additionalPropertiesType = param.additionalProperties
|
||||
? Array.isArray(param.additionalProperties.type)
|
||||
? param.additionalProperties.type
|
||||
: [param.additionalProperties.type]
|
||||
: []
|
||||
const childParamsGroups = []
|
||||
|
||||
// If the parameter is an array or object there may be child params
|
||||
// If the parameter has oneOf or additionalProperties, they need to be
|
||||
// recursively read too.
|
||||
|
||||
// There are a couple operations with additionalProperties, which allows
|
||||
// the api to define input parameters with the type dictionary. These are the only
|
||||
// two operations (at the time of adding this code) that use additionalProperties
|
||||
// Create a snapshot of dependencies for a repository
|
||||
// Update a gist
|
||||
if (param.additionalProperties && additionalPropertiesType.includes('object')) {
|
||||
const keyParam = {
|
||||
type: 'object',
|
||||
name: 'key',
|
||||
description: await renderContent(
|
||||
`A user-defined key to represent an item in \`${paramKey}\`.`
|
||||
),
|
||||
isRequired: param.required,
|
||||
enum: param.enum,
|
||||
default: param.default,
|
||||
childParamsGroups: [],
|
||||
}
|
||||
keyParam.childParamsGroups.push(
|
||||
...(await getBodyParams(param.additionalProperties, false, summary, depth + 1))
|
||||
)
|
||||
childParamsGroups.push(keyParam)
|
||||
} else if (paramType && paramType.includes('array')) {
|
||||
const arrayType = param.items.type
|
||||
if (arrayType) {
|
||||
paramType.splice(paramType.indexOf('array'), 1, `array of ${arrayType}s`)
|
||||
}
|
||||
if (arrayType === 'object') {
|
||||
childParamsGroups.push(...(await getBodyParams(param.items, false, summary, depth + 1)))
|
||||
}
|
||||
} else if (paramType && paramType.includes('object')) {
|
||||
childParamsGroups.push(...(await getBodyParams(param, false, summary, depth + 1)))
|
||||
} else if (param && param.oneOf) {
|
||||
// get concatenated description and type
|
||||
const descriptions = []
|
||||
for (const childParam of param.oneOf) {
|
||||
paramType.push(childParam.type)
|
||||
// If there is no parent description, create a description from
|
||||
// each type
|
||||
if (!param.description) {
|
||||
if (childParam.type === 'array') {
|
||||
if (childParam.items.description) {
|
||||
descriptions.push({
|
||||
type: childParam.type,
|
||||
description: childParam.items.description,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (childParam.description) {
|
||||
descriptions.push({ type: childParam.type, description: childParam.description })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Occasionally, there is no parent description and the description
|
||||
// is in the first child parameter.
|
||||
const oneOfDescriptions = descriptions.length ? descriptions[0].description : ''
|
||||
if (!param.description) param.description = oneOfDescriptions
|
||||
}
|
||||
|
||||
// Supports backwards compatibility for OpenAPI 3.0
|
||||
// In 3.1 a nullable type is part of the param.type array and
|
||||
// the property param.nullable does not exist.
|
||||
if (param.nullable) paramType.push('null')
|
||||
paramDecorated.type = paramType.filter(Boolean).join(' or ')
|
||||
paramDecorated.name = paramKey
|
||||
if (topLevel) {
|
||||
paramDecorated.in = 'body'
|
||||
}
|
||||
paramDecorated.description = await renderContent(param.description)
|
||||
if (required.includes(paramKey)) {
|
||||
paramDecorated.isRequired = true
|
||||
}
|
||||
if (childParamsGroups.length > 0) {
|
||||
paramDecorated.childParamsGroups = childParamsGroups
|
||||
}
|
||||
if (param.enum) {
|
||||
paramDecorated.enum = param.enum
|
||||
}
|
||||
if (param.default) {
|
||||
paramDecorated.default = param.default
|
||||
}
|
||||
|
||||
bodyParametersParsed.push(paramDecorated)
|
||||
}
|
||||
|
||||
return bodyParametersParsed
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { parseTemplate } from 'url-template'
|
||||
import renderContent from '../../../lib/render-content/index.js'
|
||||
import getCodeSamples from './create-rest-examples.js'
|
||||
import operationSchema from './operation-schema.js'
|
||||
import { getBodyParams } from './get-body-params.js'
|
||||
|
||||
const { operationUrls } = JSON.parse(
|
||||
await readFile('script/rest/utils/rest-api-overrides.json', 'utf8')
|
||||
@@ -184,149 +185,3 @@ export default class Operation {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// If there is a oneOf at the top level, then we have to present just one
|
||||
// in the docs. We don't currently have a convention for showing more than one
|
||||
// set of input parameters in the docs. Having a top-level oneOf is also very
|
||||
// uncommon.
|
||||
// Currently there aren't very many operations that require this treatment.
|
||||
// As an example, the 'Add status check contexts' and 'Set status check contexts'
|
||||
// operations have a top-level oneOf.
|
||||
async function getTopLevelOneOfProperty(schema) {
|
||||
if (!schema.oneOf) {
|
||||
throw new Error('Schema does not have a requestBody oneOf property defined')
|
||||
}
|
||||
if (!(Array.isArray(schema.oneOf) && schema.oneOf.length > 0)) {
|
||||
throw new Error('Schema requestBody oneOf property is not an array')
|
||||
}
|
||||
// When a oneOf exists but the `type` differs, the case has historically
|
||||
// been that the alternate option is an array, where the first option
|
||||
// is the array as a property of the object. We need to ensure that the
|
||||
// first option listed is the most comprehensive and preferred option.
|
||||
const firstOneOfObject = schema.oneOf[0]
|
||||
const allOneOfAreObjects = schema.oneOf.every((elem) => elem.type === 'object')
|
||||
let required = firstOneOfObject.required || []
|
||||
let properties = firstOneOfObject.properties || {}
|
||||
|
||||
// When all of the oneOf objects have the `type: object` we
|
||||
// need to display all of the parameters.
|
||||
// This merges all of the properties and required values.
|
||||
if (allOneOfAreObjects) {
|
||||
for (const each of schema.oneOf.slice(1)) {
|
||||
Object.assign(firstOneOfObject.properties, each.properties)
|
||||
required = firstOneOfObject.required.concat(each.required)
|
||||
}
|
||||
properties = firstOneOfObject.properties
|
||||
}
|
||||
return { properties, required }
|
||||
}
|
||||
|
||||
// Gets the body parameters for a given schema recursively.
|
||||
async function getBodyParams(schema, topLevel = false) {
|
||||
const bodyParametersParsed = []
|
||||
const schemaObject = schema.oneOf && topLevel ? await getTopLevelOneOfProperty(schema) : schema
|
||||
const properties = schemaObject.properties || {}
|
||||
const required = schemaObject.required || []
|
||||
|
||||
for (const [paramKey, param] of Object.entries(properties)) {
|
||||
const paramDecorated = {}
|
||||
|
||||
// OpenAPI 3.0 only had a single value for `type`. OpenAPI 3.1
|
||||
// will either be a single value or an array of values.
|
||||
// This makes type an array regardless of how many values the array
|
||||
// includes. This allows us to support 3.1 while remaining backwards
|
||||
// compatible with 3.0.
|
||||
const paramType = Array.isArray(param.type) ? param.type : [param.type]
|
||||
// Supports backwards compatibility for OpenAPI 3.0
|
||||
// In 3.1 a nullable type is part of the param.type array and
|
||||
// the property param.nullable does not exist.
|
||||
if (param.nullable) paramType.push('null')
|
||||
|
||||
const additionalPropertiesType = param.additionalProperties
|
||||
? Array.isArray(param.additionalProperties.type)
|
||||
? param.additionalProperties.type
|
||||
: [param.additionalProperties.type]
|
||||
: []
|
||||
const childParamsGroups = []
|
||||
|
||||
// If the parameter is an array or object there may be child params
|
||||
// If the parameter has oneOf or additionalProperties, they need to be
|
||||
// recursively read too.
|
||||
|
||||
// There are a couple operations with additionalProperties, which allows
|
||||
// the api to define input parameters with the type dictionary. These are the only
|
||||
// two operations (at the time of adding this code) that use additionalProperties
|
||||
// Create a snapshot of dependencies for a repository
|
||||
// Update a gist
|
||||
if (param.additionalProperties && additionalPropertiesType.includes('object')) {
|
||||
const keyParam = {
|
||||
type: 'object',
|
||||
name: 'key',
|
||||
description: `<p>A user-defined key to represent an item in <code>${paramKey}</code>.</p>`,
|
||||
isRequired: param.required,
|
||||
enum: param.enum,
|
||||
default: param.default,
|
||||
childParamsGroups: [],
|
||||
}
|
||||
keyParam.childParamsGroups.push(...(await getBodyParams(param.additionalProperties)))
|
||||
childParamsGroups.push(keyParam)
|
||||
} else if (paramType && paramType.includes('array')) {
|
||||
const arrayType = param.items.type
|
||||
if (arrayType) {
|
||||
paramType.splice(paramType.indexOf('array'), 1, `array of ${arrayType}s`)
|
||||
}
|
||||
if (arrayType === 'object') {
|
||||
childParamsGroups.push(...(await getBodyParams(param.items)))
|
||||
}
|
||||
} else if (paramType && paramType.includes('object')) {
|
||||
childParamsGroups.push(...(await getBodyParams(param)))
|
||||
} else if (param && param.oneOf) {
|
||||
// get concatenated description and type
|
||||
const descriptions = []
|
||||
for (const childParam of param.oneOf) {
|
||||
paramType.push(childParam.type)
|
||||
// If there is no parent description, create a description from
|
||||
// each type
|
||||
if (!param.description) {
|
||||
if (childParam.type === 'array') {
|
||||
if (childParam.items.description) {
|
||||
descriptions.push({
|
||||
type: childParam.type,
|
||||
description: childParam.items.description,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (childParam.description) {
|
||||
descriptions.push({ type: childParam.type, description: childParam.description })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Occasionally, there is no parent description and the description
|
||||
// is in the first child parameter.
|
||||
const oneOfDescriptions = descriptions.length ? descriptions[0].description : ''
|
||||
if (!param.description) param.description = oneOfDescriptions
|
||||
}
|
||||
|
||||
paramDecorated.type = paramType.filter(Boolean).join(' or ')
|
||||
paramDecorated.name = paramKey
|
||||
if (topLevel) {
|
||||
paramDecorated.in = 'body'
|
||||
}
|
||||
paramDecorated.description = await renderContent(param.description)
|
||||
if (required.includes(paramKey)) {
|
||||
paramDecorated.isRequired = true
|
||||
}
|
||||
if (childParamsGroups.length > 0) {
|
||||
paramDecorated.childParamsGroups = childParamsGroups
|
||||
}
|
||||
if (param.enum) {
|
||||
paramDecorated.enum = param.enum
|
||||
}
|
||||
if (param.default) {
|
||||
paramDecorated.default = param.default
|
||||
}
|
||||
bodyParametersParsed.push(paramDecorated)
|
||||
}
|
||||
return bodyParametersParsed
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user