docs-util: fixes in schema factory + other improvements (#8874)
- Split schema factory for request / response / common types - Fix overlapping types "string" and "regexp" to be just one string type - If `oneOf` or `allOf` OAS schemas have one item, just use that item's type.
This commit is contained in:
@@ -546,6 +546,78 @@ class KnowledgeBaseFactory {
|
||||
return `Whether the ${singular(options.parentName)} was deleted.`
|
||||
},
|
||||
},
|
||||
{
|
||||
exact: "$eq",
|
||||
template: `Filter by an exact match.`,
|
||||
},
|
||||
{
|
||||
exact: "$ne",
|
||||
template: `Filter by values not equal to this parameter.`,
|
||||
},
|
||||
{
|
||||
exact: "$in",
|
||||
template: `Filter by values in this array.`,
|
||||
},
|
||||
{
|
||||
exact: "$nin",
|
||||
template: `Filter by values not in this array.`,
|
||||
},
|
||||
{
|
||||
exact: "$not",
|
||||
template: `Filter by values not matching the conditions in this parameter.`,
|
||||
},
|
||||
{
|
||||
exact: "$gt",
|
||||
template: `Filter by values greater than this parameter. Useful for numbers and dates only.`,
|
||||
},
|
||||
{
|
||||
exact: "$gte",
|
||||
template: `Filter by values greater than or equal to this parameter. Useful for numbers and dates only.`,
|
||||
},
|
||||
{
|
||||
exact: "$lt",
|
||||
template: `Filter by values less than this parameter. Useful for numbers and dates only.`,
|
||||
},
|
||||
{
|
||||
exact: "$lte",
|
||||
template: `Filter by values less than or equal to this parameter. Useful for numbers and dates only.`,
|
||||
},
|
||||
{
|
||||
exact: "$like",
|
||||
template: "Apply a `like` filter. Useful for strings only.",
|
||||
},
|
||||
{
|
||||
exact: "$re",
|
||||
template: "Apply a regex filter. Useful for strings only.",
|
||||
},
|
||||
{
|
||||
exact: "$ilike",
|
||||
template:
|
||||
"Apply a case-insensitive `like` filter. Useful for strings only.",
|
||||
},
|
||||
{
|
||||
exact: "$fulltext",
|
||||
template: "Filter to apply on full-text properties.",
|
||||
},
|
||||
{
|
||||
exact: "$overlap",
|
||||
template:
|
||||
"Filter arrays that have overlapping values with this parameter.",
|
||||
},
|
||||
{
|
||||
exact: "$contains",
|
||||
template:
|
||||
"Filter arrays that contain some of the values of this parameter.",
|
||||
},
|
||||
{
|
||||
exact: "$contained",
|
||||
template: "Filter arrays that contain all values of this parameter.",
|
||||
},
|
||||
{
|
||||
exact: "$exists",
|
||||
template:
|
||||
"Filter by whether a value for this parameter exists (not `null`).",
|
||||
},
|
||||
{
|
||||
pattern: /.*/,
|
||||
template(str, options) {
|
||||
|
||||
@@ -9,24 +9,6 @@ class SchemaFactory {
|
||||
* The pre-defined schemas.
|
||||
*/
|
||||
private schemas: Record<string, OpenApiSchema> = {
|
||||
BigNumberInput: {
|
||||
type: "string",
|
||||
},
|
||||
BigNumber: {
|
||||
type: "string",
|
||||
},
|
||||
created_at: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
},
|
||||
updated_at: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
},
|
||||
deleted_at: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
},
|
||||
$and: {
|
||||
type: "array",
|
||||
description:
|
||||
@@ -43,6 +25,29 @@ class SchemaFactory {
|
||||
type: "object",
|
||||
},
|
||||
},
|
||||
BigNumberInput: {
|
||||
type: "string",
|
||||
},
|
||||
BigNumber: {
|
||||
type: "string",
|
||||
},
|
||||
}
|
||||
/**
|
||||
* Schemas used only for response types.
|
||||
*/
|
||||
private schemasForResponse: Record<string, OpenApiSchema> = {
|
||||
created_at: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
},
|
||||
updated_at: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
},
|
||||
deleted_at: {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,13 +59,21 @@ class SchemaFactory {
|
||||
*/
|
||||
public tryGetSchema(
|
||||
name: string,
|
||||
additionalData?: Partial<OpenApiSchema>
|
||||
additionalData?: Partial<OpenApiSchema>,
|
||||
type: "request" | "response" | "all" = "all"
|
||||
): OpenApiSchema | undefined {
|
||||
if (!Object.hasOwn(this.schemas, name)) {
|
||||
const schemasFactory =
|
||||
type === "response"
|
||||
? this.mergeSchemas(this.schemasForResponse, this.schemas)
|
||||
: this.cloneSchema(this.schemas)
|
||||
const key = Object.hasOwn(schemasFactory, name)
|
||||
? name
|
||||
: additionalData?.title || ""
|
||||
if (!Object.hasOwn(schemasFactory, key)) {
|
||||
return
|
||||
}
|
||||
|
||||
let schema = Object.assign({}, this.schemas[name])
|
||||
let schema = Object.assign({}, schemasFactory[key])
|
||||
|
||||
if (additionalData) {
|
||||
schema = Object.assign(schema, additionalData)
|
||||
@@ -68,6 +81,19 @@ class SchemaFactory {
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
private mergeSchemas(
|
||||
main: Record<string, OpenApiSchema>,
|
||||
other: Record<string, OpenApiSchema>
|
||||
): Record<string, OpenApiSchema> {
|
||||
return Object.assign(this.cloneSchema(main), this.cloneSchema(other))
|
||||
}
|
||||
|
||||
private cloneSchema(
|
||||
schema: Record<string, OpenApiSchema>
|
||||
): Record<string, OpenApiSchema> {
|
||||
return JSON.parse(JSON.stringify(schema))
|
||||
}
|
||||
}
|
||||
|
||||
export default SchemaFactory
|
||||
|
||||
@@ -978,6 +978,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
itemType: propertyType,
|
||||
title: property.getName(),
|
||||
descriptionOptions,
|
||||
context: "request",
|
||||
}),
|
||||
})
|
||||
)
|
||||
@@ -999,6 +1000,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
rawParentName: this.checker.typeToString(requestTypeArguments[0]),
|
||||
},
|
||||
zodObjectTypeName: zodObjectTypeName,
|
||||
context: "request",
|
||||
})
|
||||
|
||||
// If function is a GET function, add the type parameter to the
|
||||
@@ -1105,6 +1107,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
typeReferenceNode: node.parameters[1].type,
|
||||
itemType: responseTypeArguments[0],
|
||||
}),
|
||||
context: "response",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1126,6 +1129,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
allowedChildren,
|
||||
disallowedChildren,
|
||||
zodObjectTypeName,
|
||||
...rest
|
||||
}: {
|
||||
/**
|
||||
* The TypeScript type.
|
||||
@@ -1159,6 +1163,10 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
* generated type name.
|
||||
*/
|
||||
zodObjectTypeName?: string
|
||||
/**
|
||||
* Whether the type is in a request / response
|
||||
*/
|
||||
context?: "request" | "response"
|
||||
}): OpenApiSchema {
|
||||
if (level > this.MAX_LEVEL) {
|
||||
return {}
|
||||
@@ -1183,7 +1191,8 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
{
|
||||
title: title || typeAsString,
|
||||
description,
|
||||
}
|
||||
},
|
||||
rest.context
|
||||
)
|
||||
|
||||
if (schemaFromFactory) {
|
||||
@@ -1204,16 +1213,19 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
})
|
||||
return {
|
||||
type: "string",
|
||||
description,
|
||||
enum: enumMembers,
|
||||
}
|
||||
case itemType.isLiteral():
|
||||
case itemType.isLiteral() || typeAsString === "RegExp":
|
||||
const isString =
|
||||
itemType.flags === ts.TypeFlags.StringLiteral ||
|
||||
typeAsString === "RegExp"
|
||||
return {
|
||||
type:
|
||||
itemType.flags === ts.TypeFlags.StringLiteral
|
||||
? "string"
|
||||
: itemType.flags === ts.TypeFlags.NumberLiteral
|
||||
? "number"
|
||||
: "boolean",
|
||||
type: isString
|
||||
? "string"
|
||||
: itemType.flags === ts.TypeFlags.NumberLiteral
|
||||
? "number"
|
||||
: "boolean",
|
||||
title: title || typeAsString,
|
||||
description,
|
||||
format: this.getSchemaTypeFormat({
|
||||
@@ -1268,6 +1280,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
parentName: title || descriptionOptions?.parentName,
|
||||
}
|
||||
: undefined,
|
||||
...rest,
|
||||
}),
|
||||
}
|
||||
case itemType.isUnion():
|
||||
@@ -1279,39 +1292,57 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
if (allLiteral) {
|
||||
return {
|
||||
type: "string",
|
||||
description,
|
||||
enum: (itemType as ts.UnionType).types.map(
|
||||
(unionType) => (unionType as ts.LiteralType).value
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const oneOfItems = this.removeStringRegExpTypeOverlaps(
|
||||
(itemType as ts.UnionType).types
|
||||
).map((unionType) =>
|
||||
this.typeToSchema({
|
||||
itemType: unionType,
|
||||
// not incrementing considering the
|
||||
// current schema isn't actually a
|
||||
// schema
|
||||
level,
|
||||
title,
|
||||
descriptionOptions,
|
||||
...rest,
|
||||
})
|
||||
)
|
||||
|
||||
if (oneOfItems.length === 1) {
|
||||
return oneOfItems[0]
|
||||
}
|
||||
|
||||
return {
|
||||
oneOf: (itemType as ts.UnionType).types.map((unionType) =>
|
||||
this.typeToSchema({
|
||||
itemType: unionType,
|
||||
// not incrementing considering the
|
||||
// current schema isn't actually a
|
||||
// schema
|
||||
level,
|
||||
title,
|
||||
descriptionOptions,
|
||||
})
|
||||
),
|
||||
oneOf: oneOfItems,
|
||||
}
|
||||
case itemType.isIntersection():
|
||||
const allOfItems = this.removeStringRegExpTypeOverlaps(
|
||||
(itemType as ts.IntersectionType).types
|
||||
).map((intersectionType) => {
|
||||
return this.typeToSchema({
|
||||
itemType: intersectionType,
|
||||
// not incrementing considering the
|
||||
// current schema isn't actually a
|
||||
// schema
|
||||
level,
|
||||
title,
|
||||
descriptionOptions,
|
||||
...rest,
|
||||
})
|
||||
})
|
||||
|
||||
if (allOfItems.length === 1) {
|
||||
return allOfItems[0]
|
||||
}
|
||||
|
||||
return {
|
||||
allOf: (itemType as ts.IntersectionType).types.map(
|
||||
(intersectionType) => {
|
||||
return this.typeToSchema({
|
||||
itemType: intersectionType,
|
||||
// not incrementing considering the
|
||||
// current schema isn't actually a
|
||||
// schema
|
||||
level,
|
||||
title,
|
||||
descriptionOptions,
|
||||
})
|
||||
}
|
||||
),
|
||||
allOf: allOfItems,
|
||||
}
|
||||
case typeAsString.startsWith("Pick"):
|
||||
const pickTypeArgs =
|
||||
@@ -1332,6 +1363,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
level,
|
||||
descriptionOptions,
|
||||
allowedChildren: pickedProperties,
|
||||
...rest,
|
||||
})
|
||||
case typeAsString.startsWith("Omit"):
|
||||
const omitTypeArgs =
|
||||
@@ -1352,6 +1384,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
level,
|
||||
descriptionOptions,
|
||||
disallowedChildren: omitProperties,
|
||||
...rest,
|
||||
})
|
||||
case typeAsString.startsWith("Partial"):
|
||||
const typeArg =
|
||||
@@ -1368,6 +1401,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
descriptionOptions,
|
||||
disallowedChildren,
|
||||
allowedChildren,
|
||||
...rest,
|
||||
})
|
||||
|
||||
// remove all required items
|
||||
@@ -1405,6 +1439,7 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
typeStr: property.name,
|
||||
parentName: title || descriptionOptions?.parentName,
|
||||
},
|
||||
...rest,
|
||||
})
|
||||
|
||||
if (isDeleteResponse && property.name === "object") {
|
||||
@@ -2034,6 +2069,23 @@ class OasKindGenerator extends FunctionKindGenerator {
|
||||
fnText.includes(indicator)
|
||||
)
|
||||
}
|
||||
|
||||
private removeStringRegExpTypeOverlaps(types: ts.Type[]): ts.Type[] {
|
||||
return types.filter((itemType) => {
|
||||
// remove overlapping string / regexp types
|
||||
if (this.checker.typeToString(itemType) === "RegExp") {
|
||||
const hasString = types.some((t) => {
|
||||
return (
|
||||
t.flags === ts.TypeFlags.String ||
|
||||
t.flags === ts.TypeFlags.StringLiteral
|
||||
)
|
||||
})
|
||||
return !hasString
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default OasKindGenerator
|
||||
|
||||
Reference in New Issue
Block a user