docs-util: fixes and improvements to OAS generator (#7731)
* fixed associated schema names * move OAS method and summary descriptions to knowledge base * refactoring and fixes * detect default object value in delete responses
This commit is contained in:
1 parent
0462cc5acf
commit
922fff4051
7 files changed
+193
-172
No files matched your search
@@ -1,8 +1,9 @@
|
|||||||
import { faker } from "@faker-js/faker"
|
import { faker } from "@faker-js/faker"
|
||||||
import { OpenAPIV3 } from "openapi-types"
|
import { OpenAPIV3 } from "openapi-types"
|
||||||
import { API_ROUTE_PARAM_REGEX, OasArea } from "../kinds/oas.js"
|
import { OasArea } from "../kinds/oas.js"
|
||||||
import { CodeSample } from "../../types/index.js"
|
import { CodeSample } from "../../types/index.js"
|
||||||
import { capitalize, kebabToCamel, wordsToCamel, wordsToKebab } from "utils"
|
import { capitalize, kebabToCamel, wordsToCamel, wordsToKebab } from "utils"
|
||||||
|
import { API_ROUTE_PARAM_REGEX } from "../../constants.js"
|
||||||
|
|
||||||
type CodeSampleData = Omit<CodeSample, "source">
|
type CodeSampleData = Omit<CodeSample, "source">
|
||||||
|
|
||||||
|
|||||||
+133
-3
@@ -1,7 +1,18 @@
|
|||||||
import ts from "typescript"
|
import ts from "typescript"
|
||||||
import { DOCBLOCK_DOUBLE_LINES, DOCBLOCK_NEW_LINE } from "../../constants.js"
|
import {
|
||||||
|
API_ROUTE_PARAM_REGEX,
|
||||||
|
DOCBLOCK_DOUBLE_LINES,
|
||||||
|
DOCBLOCK_NEW_LINE,
|
||||||
|
SUMMARY_PLACEHOLDER,
|
||||||
|
} from "../../constants.js"
|
||||||
import pluralize from "pluralize"
|
import pluralize from "pluralize"
|
||||||
import { camelToTitle, camelToWords, snakeToWords } from "utils"
|
import {
|
||||||
|
camelToTitle,
|
||||||
|
camelToWords,
|
||||||
|
kebabToTitle,
|
||||||
|
snakeToWords,
|
||||||
|
wordsToKebab,
|
||||||
|
} from "utils"
|
||||||
import { normalizeName } from "../../utils/str-formatting.js"
|
import { normalizeName } from "../../utils/str-formatting.js"
|
||||||
|
|
||||||
type TemplateOptions = {
|
type TemplateOptions = {
|
||||||
@@ -537,7 +548,7 @@ class KnowledgeBaseFactory {
|
|||||||
*
|
*
|
||||||
* @returns {string | undefined} The matching knowledgebase template, if found.
|
* @returns {string | undefined} The matching knowledgebase template, if found.
|
||||||
*/
|
*/
|
||||||
tryToGetOasDescription({
|
tryToGetOasSchemaDescription({
|
||||||
str,
|
str,
|
||||||
...options
|
...options
|
||||||
}: RetrieveOptions): string | undefined {
|
}: RetrieveOptions): string | undefined {
|
||||||
@@ -548,6 +559,125 @@ class KnowledgeBaseFactory {
|
|||||||
knowledgeBase: this.oasDescriptionKnowledgeBase,
|
knowledgeBase: this.oasDescriptionKnowledgeBase,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Retrieve the summary and description of the OAS.
|
||||||
|
*
|
||||||
|
* @param param0 - The OAS operation's details.
|
||||||
|
* @returns The summary and description.
|
||||||
|
*/
|
||||||
|
tryToGetOasMethodSummaryAndDescription({
|
||||||
|
oasPath,
|
||||||
|
httpMethod,
|
||||||
|
tag,
|
||||||
|
}: {
|
||||||
|
/**
|
||||||
|
* The OAS path.
|
||||||
|
*/
|
||||||
|
oasPath: string
|
||||||
|
/**
|
||||||
|
* The HTTP method name.
|
||||||
|
*/
|
||||||
|
httpMethod: string
|
||||||
|
/**
|
||||||
|
* The OAS tag name.
|
||||||
|
*/
|
||||||
|
tag: string
|
||||||
|
}): {
|
||||||
|
/**
|
||||||
|
* The OAS's summary
|
||||||
|
*/
|
||||||
|
summary: string
|
||||||
|
/**
|
||||||
|
* The OAS's description.
|
||||||
|
*/
|
||||||
|
description: string
|
||||||
|
} {
|
||||||
|
// reset regex manually
|
||||||
|
API_ROUTE_PARAM_REGEX.lastIndex = 0
|
||||||
|
const result = {
|
||||||
|
summary: SUMMARY_PLACEHOLDER,
|
||||||
|
description: SUMMARY_PLACEHOLDER,
|
||||||
|
}
|
||||||
|
// retrieve different variations of the tag to include in the summary/description
|
||||||
|
const lowerTag = tag.toLowerCase()
|
||||||
|
const singularLowerTag = pluralize.singular(lowerTag)
|
||||||
|
const singularTag = pluralize.singular(tag)
|
||||||
|
|
||||||
|
// check if the OAS operation is performed on a single entity or
|
||||||
|
// general entities. If the operation has a path parameter, then it's
|
||||||
|
// considered for a single entity.
|
||||||
|
const isForSingleEntity = API_ROUTE_PARAM_REGEX.test(oasPath)
|
||||||
|
|
||||||
|
if (isForSingleEntity) {
|
||||||
|
// Check whether the OAS operation is applied on a different entity.
|
||||||
|
// If the OAS path ends with /batch or a different entity
|
||||||
|
// name than the tag name, then it's performed on an entity other than the
|
||||||
|
// main entity (the one indicated by the tag), so the summary/description vary
|
||||||
|
// slightly.
|
||||||
|
const splitOasPath = oasPath
|
||||||
|
.replaceAll(API_ROUTE_PARAM_REGEX, "")
|
||||||
|
.replace(/\/(batch)*$/, "")
|
||||||
|
.split("/")
|
||||||
|
const isBulk = oasPath.endsWith("/batch")
|
||||||
|
const isOperationOnDifferentEntity =
|
||||||
|
wordsToKebab(tag) !== splitOasPath[splitOasPath.length - 1]
|
||||||
|
|
||||||
|
if (isBulk || isOperationOnDifferentEntity) {
|
||||||
|
// if the operation is a bulk operation and it ends with a path parameter (after removing the `/batch` part)
|
||||||
|
// then the tag name is the targeted entity. Else, it's the last part of the OAS path (after removing the `/batch` part).
|
||||||
|
const endingEntityName =
|
||||||
|
isBulk &&
|
||||||
|
API_ROUTE_PARAM_REGEX.test(splitOasPath[splitOasPath.length - 1])
|
||||||
|
? tag
|
||||||
|
: kebabToTitle(splitOasPath[splitOasPath.length - 1])
|
||||||
|
// retrieve different formatted versions of the entity name for the summary/description
|
||||||
|
const pluralEndingEntityName = pluralize.plural(endingEntityName)
|
||||||
|
const lowerEndingEntityName = pluralEndingEntityName.toLowerCase()
|
||||||
|
const singularLowerEndingEntityName =
|
||||||
|
pluralize.singular(endingEntityName)
|
||||||
|
|
||||||
|
// set the summary/description based on the HTTP method
|
||||||
|
if (httpMethod === "get") {
|
||||||
|
result.summary = `List ${pluralEndingEntityName}`
|
||||||
|
result.description = `Retrieve a list of ${lowerEndingEntityName} in a ${singularLowerTag}. The ${lowerEndingEntityName} can be filtered by fields like FILTER FIELDS. The ${lowerEndingEntityName} can also be paginated.`
|
||||||
|
} else if (httpMethod === "post") {
|
||||||
|
result.summary = `Add ${pluralEndingEntityName} to ${singularTag}`
|
||||||
|
result.description = `Add a list of ${lowerEndingEntityName} to a ${singularLowerTag}.`
|
||||||
|
} else {
|
||||||
|
result.summary = `Remove ${pluralEndingEntityName} from ${singularTag}`
|
||||||
|
result.description = `Remove a list of ${lowerEndingEntityName} from a ${singularLowerTag}. This doesn't delete the ${singularLowerEndingEntityName}, only the association between the ${singularLowerEndingEntityName} and the ${singularLowerTag}.`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the OAS operation is applied on a single entity that is the main entity (denoted by the tag).
|
||||||
|
// retrieve the summary/description based on the HTTP method.
|
||||||
|
if (httpMethod === "get") {
|
||||||
|
result.summary = `Get a ${singularTag}`
|
||||||
|
result.description = `Retrieve a ${singularLowerTag} by its ID. You can expand the ${singularLowerTag}'s relations or select the fields that should be returned.`
|
||||||
|
} else if (httpMethod === "post") {
|
||||||
|
result.summary = `Update a ${singularTag}`
|
||||||
|
result.description = `Update a ${singularLowerTag}'s details.`
|
||||||
|
} else {
|
||||||
|
result.summary = `Delete a ${singularTag}`
|
||||||
|
result.description = `Delete a ${singularLowerTag}.`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the OAS operation is applied on all entities of the tag in general.
|
||||||
|
// retrieve the summary/description based on the HTTP method.
|
||||||
|
if (httpMethod === "get") {
|
||||||
|
result.summary = `List ${tag}`
|
||||||
|
result.description = `Retrieve a list of ${lowerTag}. The ${lowerTag} can be filtered by fields such as \`id\`. The ${lowerTag} can also be sorted or paginated.`
|
||||||
|
} else if (httpMethod === "post") {
|
||||||
|
result.summary = `Create ${singularTag}`
|
||||||
|
result.description = `Create a ${singularLowerTag}.`
|
||||||
|
} else {
|
||||||
|
result.summary = `Delete ${tag}`
|
||||||
|
result.description = `Delete ${tag}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KnowledgeBaseFactory
|
export default KnowledgeBaseFactory
|
||||||
@@ -187,11 +187,11 @@ class OasSchemaHelper {
|
|||||||
* @param name - The schema's name
|
* @param name - The schema's name
|
||||||
* @returns The schema's file name
|
* @returns The schema's file name
|
||||||
*/
|
*/
|
||||||
getSchemaFileName(name: string): string {
|
getSchemaFileName(name: string, shouldNormalizeName = true): string {
|
||||||
return join(
|
return join(
|
||||||
this.baseOutputPath,
|
this.baseOutputPath,
|
||||||
"schemas",
|
"schemas",
|
||||||
`${this.normalizeSchemaName(name)}.ts`
|
`${shouldNormalizeName ? this.normalizeSchemaName(name) : name}.ts`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,8 +202,13 @@ class OasSchemaHelper {
|
|||||||
* @param name - The schema's name.
|
* @param name - The schema's name.
|
||||||
* @returns The parsed schema, if found.
|
* @returns The parsed schema, if found.
|
||||||
*/
|
*/
|
||||||
getSchemaByName(name: string): ParsedSchema | undefined {
|
getSchemaByName(
|
||||||
const schemaName = this.normalizeSchemaName(name)
|
name: string,
|
||||||
|
shouldNormalizeName = true
|
||||||
|
): ParsedSchema | undefined {
|
||||||
|
const schemaName = shouldNormalizeName
|
||||||
|
? this.normalizeSchemaName(name)
|
||||||
|
: name
|
||||||
// check if it already exists in the schemas map
|
// check if it already exists in the schemas map
|
||||||
if (this.schemas.has(schemaName)) {
|
if (this.schemas.has(schemaName)) {
|
||||||
return {
|
return {
|
||||||
@@ -211,7 +216,7 @@ class OasSchemaHelper {
|
|||||||
schemaPrefix: `@schema ${schemaName}`,
|
schemaPrefix: `@schema ${schemaName}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const schemaFile = this.getSchemaFileName(schemaName)
|
const schemaFile = this.getSchemaFileName(schemaName, shouldNormalizeName)
|
||||||
const schemaFileContent = ts.sys.readFile(schemaFile)
|
const schemaFileContent = ts.sys.readFile(schemaFile)
|
||||||
|
|
||||||
if (!schemaFileContent) {
|
if (!schemaFileContent) {
|
||||||
@@ -267,7 +272,7 @@ class OasSchemaHelper {
|
|||||||
return name
|
return name
|
||||||
.replace("DTO", "")
|
.replace("DTO", "")
|
||||||
.replace(this.schemaRefPrefix, "")
|
.replace(this.schemaRefPrefix, "")
|
||||||
.replace(/Type$/, "")
|
.replace(/(?<!Type)Type$/, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,13 +330,9 @@ class OasSchemaHelper {
|
|||||||
* @param tagName - The name of the tag.
|
* @param tagName - The name of the tag.
|
||||||
* @returns The possible names of the associated schema.
|
* @returns The possible names of the associated schema.
|
||||||
*/
|
*/
|
||||||
tagNameToSchemaName(tagName: string, area: OasArea): string[] {
|
tagNameToSchemaName(tagName: string, area: OasArea): string {
|
||||||
const mainSchemaName = wordsToPascal(pluralize.singular(tagName))
|
const mainSchemaName = wordsToPascal(pluralize.singular(tagName))
|
||||||
return [
|
return `${capitalize(area)}${mainSchemaName}`
|
||||||
mainSchemaName,
|
|
||||||
`${mainSchemaName}Response`,
|
|
||||||
`${capitalize(area)}Create${mainSchemaName}`,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
DOCBLOCK_END_LINE,
|
DOCBLOCK_END_LINE,
|
||||||
DOCBLOCK_DOUBLE_LINES,
|
DOCBLOCK_DOUBLE_LINES,
|
||||||
DOCBLOCK_NEW_LINE,
|
DOCBLOCK_NEW_LINE,
|
||||||
|
SUMMARY_PLACEHOLDER,
|
||||||
} from "../../constants.js"
|
} from "../../constants.js"
|
||||||
import getSymbol from "../../utils/get-symbol.js"
|
import getSymbol from "../../utils/get-symbol.js"
|
||||||
import KnowledgeBaseFactory, {
|
import KnowledgeBaseFactory, {
|
||||||
@@ -56,7 +57,6 @@ class DefaultKindGenerator<T extends ts.Node = ts.Node> {
|
|||||||
public name = "default"
|
public name = "default"
|
||||||
protected allowedKinds: ts.SyntaxKind[]
|
protected allowedKinds: ts.SyntaxKind[]
|
||||||
protected checker: ts.TypeChecker
|
protected checker: ts.TypeChecker
|
||||||
protected defaultSummary = "{summary}"
|
|
||||||
protected knowledgeBaseFactory: KnowledgeBaseFactory
|
protected knowledgeBaseFactory: KnowledgeBaseFactory
|
||||||
protected generatorEventManager: GeneratorEventManager
|
protected generatorEventManager: GeneratorEventManager
|
||||||
protected options: Pick<CommonCliOptions, "generateExamples">
|
protected options: Pick<CommonCliOptions, "generateExamples">
|
||||||
@@ -186,7 +186,7 @@ class DefaultKindGenerator<T extends ts.Node = ts.Node> {
|
|||||||
summary = this.getTypeDocBlock(nodeType, knowledgeBaseOptions)
|
summary = this.getTypeDocBlock(nodeType, knowledgeBaseOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
return summary.length > 0 ? summary : this.defaultSummary
|
return summary.length > 0 ? summary : SUMMARY_PLACEHOLDER
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -234,7 +234,7 @@ class DefaultKindGenerator<T extends ts.Node = ts.Node> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// do some formatting if the encapsulating type is an array
|
// do some formatting if the encapsulating type is an array
|
||||||
return `The list of ${capitalize(typeArgumentDoc) || this.defaultSummary}`
|
return `The list of ${capitalize(typeArgumentDoc) || SUMMARY_PLACEHOLDER}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -360,7 +360,7 @@ class DefaultKindGenerator<T extends ts.Node = ts.Node> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.addDefaultSummary) {
|
if (options.addDefaultSummary) {
|
||||||
tags.add(this.defaultSummary)
|
tags.add(SUMMARY_PLACEHOLDER)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for private or protected modifiers
|
// check for private or protected modifiers
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
DOCBLOCK_END_LINE,
|
DOCBLOCK_END_LINE,
|
||||||
DOCBLOCK_START,
|
DOCBLOCK_START,
|
||||||
DOCBLOCK_DOUBLE_LINES,
|
DOCBLOCK_DOUBLE_LINES,
|
||||||
|
SUMMARY_PLACEHOLDER,
|
||||||
} from "../../constants.js"
|
} from "../../constants.js"
|
||||||
import getSymbol from "../../utils/get-symbol.js"
|
import getSymbol from "../../utils/get-symbol.js"
|
||||||
import AiGenerator from "../helpers/ai-generator.js"
|
import AiGenerator from "../helpers/ai-generator.js"
|
||||||
@@ -327,7 +328,7 @@ class FunctionKindGenerator extends DefaultKindGenerator<FunctionOrVariableNode>
|
|||||||
|
|
||||||
// add returns
|
// add returns
|
||||||
const possibleReturnSummary = !this.hasReturnData(returnTypeStr)
|
const possibleReturnSummary = !this.hasReturnData(returnTypeStr)
|
||||||
? `Resolves when ${this.defaultSummary}`
|
? `Resolves when ${SUMMARY_PLACEHOLDER}`
|
||||||
: this.getNodeSummary({
|
: this.getNodeSummary({
|
||||||
node: actualNode,
|
node: actualNode,
|
||||||
nodeType,
|
nodeType,
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { readFileSync, writeFileSync } from "fs"
|
import { readFileSync, writeFileSync } from "fs"
|
||||||
import { OpenAPIV3 } from "openapi-types"
|
import { OpenAPIV3 } from "openapi-types"
|
||||||
import { basename, join } from "path"
|
import { basename, join } from "path"
|
||||||
import pluralize from "pluralize"
|
|
||||||
import ts, { SyntaxKind } from "typescript"
|
import ts, { SyntaxKind } from "typescript"
|
||||||
import { capitalize, kebabToTitle, wordsToKebab } from "utils"
|
import { capitalize, kebabToTitle } from "utils"
|
||||||
import { parse, stringify } from "yaml"
|
import { parse, stringify } from "yaml"
|
||||||
import { DEFAULT_OAS_RESPONSES } from "../../constants.js"
|
import { DEFAULT_OAS_RESPONSES, SUMMARY_PLACEHOLDER } from "../../constants.js"
|
||||||
import {
|
import {
|
||||||
OpenApiDocument,
|
OpenApiDocument,
|
||||||
OpenApiOperation,
|
OpenApiOperation,
|
||||||
@@ -18,7 +17,7 @@ import isZodObject from "../../utils/is-zod-object.js"
|
|||||||
import parseOas, { ExistingOas } from "../../utils/parse-oas.js"
|
import parseOas, { ExistingOas } from "../../utils/parse-oas.js"
|
||||||
import OasExamplesGenerator from "../examples/oas.js"
|
import OasExamplesGenerator from "../examples/oas.js"
|
||||||
import { GeneratorEvent } from "../helpers/generator-event-manager.js"
|
import { GeneratorEvent } from "../helpers/generator-event-manager.js"
|
||||||
import OasSchemaHelper, { ParsedSchema } from "../helpers/oas-schema.js"
|
import OasSchemaHelper from "../helpers/oas-schema.js"
|
||||||
import SchemaFactory from "../helpers/schema-factory.js"
|
import SchemaFactory from "../helpers/schema-factory.js"
|
||||||
import { GeneratorOptions, GetDocBlockOptions } from "./default.js"
|
import { GeneratorOptions, GetDocBlockOptions } from "./default.js"
|
||||||
import FunctionKindGenerator, {
|
import FunctionKindGenerator, {
|
||||||
@@ -26,8 +25,8 @@ import FunctionKindGenerator, {
|
|||||||
FunctionOrVariableNode,
|
FunctionOrVariableNode,
|
||||||
VariableNode,
|
VariableNode,
|
||||||
} from "./function.js"
|
} from "./function.js"
|
||||||
|
import { API_ROUTE_PARAM_REGEX } from "../helpers/../../constants.js"
|
||||||
|
|
||||||
export const API_ROUTE_PARAM_REGEX = /\[(.+?)\]/g
|
|
||||||
const RES_STATUS_REGEX = /^res[\s\S]*\.status\((\d+)\)/
|
const RES_STATUS_REGEX = /^res[\s\S]*\.status\((\d+)\)/
|
||||||
|
|
||||||
type SchemaDescriptionOptions = {
|
type SchemaDescriptionOptions = {
|
||||||
@@ -50,11 +49,6 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
public name = "oas"
|
public name = "oas"
|
||||||
protected allowedKinds: SyntaxKind[] = [ts.SyntaxKind.FunctionDeclaration]
|
protected allowedKinds: SyntaxKind[] = [ts.SyntaxKind.FunctionDeclaration]
|
||||||
private MAX_LEVEL = 4
|
private MAX_LEVEL = 4
|
||||||
// we can't use `{summary}` because it causes an MDX error
|
|
||||||
// when we finally render the summary. We can alternatively
|
|
||||||
// use `\{summary\}` but it wouldn't look pretty in the OAS,
|
|
||||||
// so doing this for now.
|
|
||||||
protected defaultSummary = "SUMMARY"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This map collects tags of all the generated OAS, then, once the generation process finishes,
|
* This map collects tags of all the generated OAS, then, once the generation process finishes,
|
||||||
@@ -228,7 +222,8 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
const { isAdminAuthenticated, isStoreAuthenticated, isAuthenticated } =
|
const { isAdminAuthenticated, isStoreAuthenticated, isAuthenticated } =
|
||||||
this.getAuthenticationDetails(node, oasPath)
|
this.getAuthenticationDetails(node, oasPath)
|
||||||
const tagName = this.getTagName(splitOasPath)
|
const tagName = this.getTagName(splitOasPath)
|
||||||
const { summary, description } = this.getSummaryAndDescription({
|
const { summary, description } =
|
||||||
|
this.knowledgeBaseFactory.tryToGetOasMethodSummaryAndDescription({
|
||||||
oasPath,
|
oasPath,
|
||||||
httpMethod: methodName,
|
httpMethod: methodName,
|
||||||
tag: tagName || "",
|
tag: tagName || "",
|
||||||
@@ -405,11 +400,12 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
|
|
||||||
// update summary and description either if they're empty or default summary
|
// update summary and description either if they're empty or default summary
|
||||||
const shouldUpdateSummary =
|
const shouldUpdateSummary =
|
||||||
!oas.summary || oas.summary === this.defaultSummary
|
!oas.summary || oas.summary === SUMMARY_PLACEHOLDER
|
||||||
const shouldUpdateDescription =
|
const shouldUpdateDescription =
|
||||||
!oas.description || oas.description === this.defaultSummary
|
!oas.description || oas.description === SUMMARY_PLACEHOLDER
|
||||||
if (shouldUpdateSummary || shouldUpdateDescription) {
|
if (shouldUpdateSummary || shouldUpdateDescription) {
|
||||||
const { summary, description } = this.getSummaryAndDescription({
|
const { summary, description } =
|
||||||
|
this.knowledgeBaseFactory.tryToGetOasMethodSummaryAndDescription({
|
||||||
oasPath,
|
oasPath,
|
||||||
httpMethod: methodName,
|
httpMethod: methodName,
|
||||||
tag: tagName || "",
|
tag: tagName || "",
|
||||||
@@ -775,126 +771,6 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the summary and description of the OAS.
|
|
||||||
*
|
|
||||||
* @param param0 - The OAS operation's details.
|
|
||||||
* @returns The summary and description.
|
|
||||||
*/
|
|
||||||
getSummaryAndDescription({
|
|
||||||
oasPath,
|
|
||||||
httpMethod,
|
|
||||||
tag,
|
|
||||||
}: {
|
|
||||||
/**
|
|
||||||
* The OAS path.
|
|
||||||
*/
|
|
||||||
oasPath: string
|
|
||||||
/**
|
|
||||||
* The HTTP method name.
|
|
||||||
*/
|
|
||||||
httpMethod: string
|
|
||||||
/**
|
|
||||||
* The OAS tag name.
|
|
||||||
*/
|
|
||||||
tag: string
|
|
||||||
}): {
|
|
||||||
/**
|
|
||||||
* The OAS's summary
|
|
||||||
*/
|
|
||||||
summary: string
|
|
||||||
/**
|
|
||||||
* The OAS's description.
|
|
||||||
*/
|
|
||||||
description: string
|
|
||||||
} {
|
|
||||||
// reset regex manually
|
|
||||||
API_ROUTE_PARAM_REGEX.lastIndex = 0
|
|
||||||
const result = {
|
|
||||||
summary: this.defaultSummary,
|
|
||||||
description: this.defaultSummary,
|
|
||||||
}
|
|
||||||
// retrieve different variations of the tag to include in the summary/description
|
|
||||||
const lowerTag = tag.toLowerCase()
|
|
||||||
const singularLowerTag = pluralize.singular(lowerTag)
|
|
||||||
const singularTag = pluralize.singular(tag)
|
|
||||||
|
|
||||||
// check if the OAS operation is performed on a single entity or
|
|
||||||
// general entities. If the operation has a path parameter, then it's
|
|
||||||
// considered for a single entity.
|
|
||||||
const isForSingleEntity = API_ROUTE_PARAM_REGEX.test(oasPath)
|
|
||||||
|
|
||||||
if (isForSingleEntity) {
|
|
||||||
// Check whether the OAS operation is applied on a different entity.
|
|
||||||
// If the OAS path ends with /batch or a different entity
|
|
||||||
// name than the tag name, then it's performed on an entity other than the
|
|
||||||
// main entity (the one indicated by the tag), so the summary/description vary
|
|
||||||
// slightly.
|
|
||||||
const splitOasPath = oasPath
|
|
||||||
.replaceAll(API_ROUTE_PARAM_REGEX, "")
|
|
||||||
.replace(/\/(batch)*$/, "")
|
|
||||||
.split("/")
|
|
||||||
const isBulk = oasPath.endsWith("/batch")
|
|
||||||
const isOperationOnDifferentEntity =
|
|
||||||
wordsToKebab(tag) !== splitOasPath[splitOasPath.length - 1]
|
|
||||||
|
|
||||||
if (isBulk || isOperationOnDifferentEntity) {
|
|
||||||
// if the operation is a bulk operation and it ends with a path parameter (after removing the `/batch` part)
|
|
||||||
// then the tag name is the targeted entity. Else, it's the last part of the OAS path (after removing the `/batch` part).
|
|
||||||
const endingEntityName =
|
|
||||||
isBulk &&
|
|
||||||
API_ROUTE_PARAM_REGEX.test(splitOasPath[splitOasPath.length - 1])
|
|
||||||
? tag
|
|
||||||
: kebabToTitle(splitOasPath[splitOasPath.length - 1])
|
|
||||||
// retrieve different formatted versions of the entity name for the summary/description
|
|
||||||
const pluralEndingEntityName = pluralize.plural(endingEntityName)
|
|
||||||
const lowerEndingEntityName = pluralEndingEntityName.toLowerCase()
|
|
||||||
const singularLowerEndingEntityName =
|
|
||||||
pluralize.singular(endingEntityName)
|
|
||||||
|
|
||||||
// set the summary/description based on the HTTP method
|
|
||||||
if (httpMethod === "get") {
|
|
||||||
result.summary = `List ${pluralEndingEntityName}`
|
|
||||||
result.description = `Retrieve a list of ${lowerEndingEntityName} in a ${singularLowerTag}. The ${lowerEndingEntityName} can be filtered by fields like FILTER FIELDS. The ${lowerEndingEntityName} can also be paginated.`
|
|
||||||
} else if (httpMethod === "post") {
|
|
||||||
result.summary = `Add ${pluralEndingEntityName} to ${singularTag}`
|
|
||||||
result.description = `Add a list of ${lowerEndingEntityName} to a ${singularLowerTag}.`
|
|
||||||
} else {
|
|
||||||
result.summary = `Remove ${pluralEndingEntityName} from ${singularTag}`
|
|
||||||
result.description = `Remove a list of ${lowerEndingEntityName} from a ${singularLowerTag}. This doesn't delete the ${singularLowerEndingEntityName}, only the association between the ${singularLowerEndingEntityName} and the ${singularLowerTag}.`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// the OAS operation is applied on a single entity that is the main entity (denoted by the tag).
|
|
||||||
// retrieve the summary/description based on the HTTP method.
|
|
||||||
if (httpMethod === "get") {
|
|
||||||
result.summary = `Get a ${singularTag}`
|
|
||||||
result.description = `Retrieve a ${singularLowerTag} by its ID. You can expand the ${singularLowerTag}'s relations or select the fields that should be returned.`
|
|
||||||
} else if (httpMethod === "post") {
|
|
||||||
result.summary = `Update a ${singularTag}`
|
|
||||||
result.description = `Update a ${singularLowerTag}'s details.`
|
|
||||||
} else {
|
|
||||||
result.summary = `Delete a ${singularTag}`
|
|
||||||
result.description = `Delete a ${singularLowerTag}.`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// the OAS operation is applied on all entities of the tag in general.
|
|
||||||
// retrieve the summary/description based on the HTTP method.
|
|
||||||
if (httpMethod === "get") {
|
|
||||||
result.summary = `List ${tag}`
|
|
||||||
result.description = `Retrieve a list of ${lowerTag}. The ${lowerTag} can be filtered by fields such as \`id\`. The ${lowerTag} can also be sorted or paginated.`
|
|
||||||
} else if (httpMethod === "post") {
|
|
||||||
result.summary = `Create ${singularTag}`
|
|
||||||
result.description = `Create a ${singularLowerTag}.`
|
|
||||||
} else {
|
|
||||||
result.summary = `Delete ${tag}`
|
|
||||||
result.description = `Delete ${tag}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the security details of an OAS operation.
|
* Retrieve the security details of an OAS operation.
|
||||||
*
|
*
|
||||||
@@ -1284,7 +1160,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
)
|
)
|
||||||
: title
|
: title
|
||||||
? this.getSchemaDescription({ typeStr: title, nodeType: itemType })
|
? this.getSchemaDescription({ typeStr: title, nodeType: itemType })
|
||||||
: this.defaultSummary
|
: SUMMARY_PLACEHOLDER
|
||||||
const typeAsString =
|
const typeAsString =
|
||||||
zodObjectTypeName || this.checker.typeToString(itemType)
|
zodObjectTypeName || this.checker.typeToString(itemType)
|
||||||
|
|
||||||
@@ -1493,6 +1369,10 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
const properties: Record<string, OpenApiSchema> = {}
|
const properties: Record<string, OpenApiSchema> = {}
|
||||||
const requiredProperties: string[] = []
|
const requiredProperties: string[] = []
|
||||||
|
|
||||||
|
const baseType = itemType.getBaseTypes()?.[0]
|
||||||
|
const isDeleteResponse =
|
||||||
|
baseType?.aliasSymbol?.getEscapedName() === "DeleteResponse"
|
||||||
|
|
||||||
if (level + 1 <= this.MAX_LEVEL) {
|
if (level + 1 <= this.MAX_LEVEL) {
|
||||||
itemType.getProperties().forEach((property) => {
|
itemType.getProperties().forEach((property) => {
|
||||||
if (
|
if (
|
||||||
@@ -1514,6 +1394,15 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
parentName: title || descriptionOptions?.parentName,
|
parentName: title || descriptionOptions?.parentName,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (isDeleteResponse && property.name === "object") {
|
||||||
|
// try to retrieve default from `DeleteResponse`'s type argument
|
||||||
|
const deleteTypeArg = baseType.aliasTypeArguments?.[0]
|
||||||
|
properties[property.name].default =
|
||||||
|
deleteTypeArg && "value" in deleteTypeArg
|
||||||
|
? (deleteTypeArg.value as string)
|
||||||
|
: properties[property.name].default
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1565,12 +1454,12 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
// either retrieve the description from the knowledge base or use
|
// either retrieve the description from the knowledge base or use
|
||||||
// the default summary
|
// the default summary
|
||||||
return (
|
return (
|
||||||
this.knowledgeBaseFactory.tryToGetOasDescription({
|
this.knowledgeBaseFactory.tryToGetOasSchemaDescription({
|
||||||
str: typeStr,
|
str: typeStr,
|
||||||
templateOptions: {
|
templateOptions: {
|
||||||
parentName,
|
parentName,
|
||||||
},
|
},
|
||||||
}) || this.defaultSummary
|
}) || SUMMARY_PLACEHOLDER
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1592,7 +1481,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
description = this.getSymbolDocBlock(symbol)
|
description = this.getSymbolDocBlock(symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
return description.length ? description : this.defaultSummary
|
return description.length ? description : SUMMARY_PLACEHOLDER
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1745,7 +1634,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
updatedParameter.description !== parameter.description &&
|
updatedParameter.description !== parameter.description &&
|
||||||
parameter.description === this.defaultSummary
|
parameter.description === SUMMARY_PLACEHOLDER
|
||||||
) {
|
) {
|
||||||
parameter.description = updatedParameter.description
|
parameter.description = updatedParameter.description
|
||||||
}
|
}
|
||||||
@@ -1775,7 +1664,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
if (
|
if (
|
||||||
(updatedParameter.schema as OpenApiSchema).description !==
|
(updatedParameter.schema as OpenApiSchema).description !==
|
||||||
(parameter.schema as OpenApiSchema).description &&
|
(parameter.schema as OpenApiSchema).description &&
|
||||||
(parameter.schema as OpenApiSchema).description === this.defaultSummary
|
(parameter.schema as OpenApiSchema).description === SUMMARY_PLACEHOLDER
|
||||||
) {
|
) {
|
||||||
;(parameter.schema as OpenApiSchema).description = (
|
;(parameter.schema as OpenApiSchema).description = (
|
||||||
updatedParameter.schema as OpenApiSchema
|
updatedParameter.schema as OpenApiSchema
|
||||||
@@ -1856,10 +1745,10 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
oldSchemaObj!.description !== newSchemaObj?.description &&
|
oldSchemaObj!.description !== newSchemaObj?.description &&
|
||||||
oldSchemaObj!.description === this.defaultSummary
|
oldSchemaObj!.description === SUMMARY_PLACEHOLDER
|
||||||
) {
|
) {
|
||||||
oldSchemaObj!.description =
|
oldSchemaObj!.description =
|
||||||
newSchemaObj?.description || this.defaultSummary
|
newSchemaObj?.description || SUMMARY_PLACEHOLDER
|
||||||
}
|
}
|
||||||
|
|
||||||
oldSchemaObj!.required = newSchemaObj?.required
|
oldSchemaObj!.required = newSchemaObj?.required
|
||||||
@@ -1971,15 +1860,8 @@ class OasKindGenerator extends FunctionKindGenerator {
|
|||||||
|
|
||||||
this.tags.get(area)?.forEach((tag) => {
|
this.tags.get(area)?.forEach((tag) => {
|
||||||
const existingTag = areaYaml.tags!.find((baseTag) => baseTag.name === tag)
|
const existingTag = areaYaml.tags!.find((baseTag) => baseTag.name === tag)
|
||||||
// try to retrieve associated schema
|
const schemaName = this.oasSchemaHelper.tagNameToSchemaName(tag, area)
|
||||||
let schema: ParsedSchema | undefined
|
const schema = this.oasSchemaHelper.getSchemaByName(schemaName, false)
|
||||||
this.oasSchemaHelper.tagNameToSchemaName(tag, area).some((schemaName) => {
|
|
||||||
schema = this.oasSchemaHelper.getSchemaByName(schemaName)
|
|
||||||
|
|
||||||
if (schema) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const associatedSchema = schema?.schema?.["x-schemaName"]
|
const associatedSchema = schema?.schema?.["x-schemaName"]
|
||||||
? {
|
? {
|
||||||
$ref: this.oasSchemaHelper.constructSchemaReference(
|
$ref: this.oasSchemaHelper.constructSchemaReference(
|
||||||
|
|||||||
@@ -27,3 +27,9 @@ export const DEFAULT_OAS_RESPONSES: {
|
|||||||
$ref: "#/components/responses/500_error",
|
$ref: "#/components/responses/500_error",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
export const API_ROUTE_PARAM_REGEX = /\[(.+?)\]/g
|
||||||
|
// we can't use `{summary}` because it causes an MDX error
|
||||||
|
// when we finally render the summary. We can alternatively
|
||||||
|
// use `\{summary\}` but it wouldn't look pretty in the OAS,
|
||||||
|
// so doing this for now.
|
||||||
|
export const SUMMARY_PLACEHOLDER = "SUMMARY"
|
||||||
Reference in new issue
Block a user