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:
Shahed Nasser authored and GitHub committed 2024-08-29 12:35:45 +00:00
1 parent 00bd9271e3
commit 9290ce88bf
3 files changed
+182 -32

No files matched your search

@@ -546,6 +546,78 @@ class KnowledgeBaseFactory {
return `Whether the ${singular(options.parentName)} was deleted.` 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: /.*/, pattern: /.*/,
template(str, options) { template(str, options) {
@@ -9,24 +9,6 @@ class SchemaFactory {
* The pre-defined schemas. * The pre-defined schemas.
*/ */
private schemas: Record<string, OpenApiSchema> = { 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: { $and: {
type: "array", type: "array",
description: description:
@@ -43,6 +25,29 @@ class SchemaFactory {
type: "object", 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( public tryGetSchema(
name: string, name: string,
additionalData?: Partial<OpenApiSchema> additionalData?: Partial<OpenApiSchema>,
type: "request" | "response" | "all" = "all"
): OpenApiSchema | undefined { ): 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 return
} }
let schema = Object.assign({}, this.schemas[name]) let schema = Object.assign({}, schemasFactory[key])
if (additionalData) { if (additionalData) {
schema = Object.assign(schema, additionalData) schema = Object.assign(schema, additionalData)
@@ -68,6 +81,19 @@ class SchemaFactory {
return schema 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 export default SchemaFactory
@@ -978,6 +978,7 @@ class OasKindGenerator extends FunctionKindGenerator {
itemType: propertyType, itemType: propertyType,
title: property.getName(), title: property.getName(),
descriptionOptions, descriptionOptions,
context: "request",
}), }),
}) })
) )
@@ -999,6 +1000,7 @@ class OasKindGenerator extends FunctionKindGenerator {
rawParentName: this.checker.typeToString(requestTypeArguments[0]), rawParentName: this.checker.typeToString(requestTypeArguments[0]),
}, },
zodObjectTypeName: zodObjectTypeName, zodObjectTypeName: zodObjectTypeName,
context: "request",
}) })
// If function is a GET function, add the type parameter to the // 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, typeReferenceNode: node.parameters[1].type,
itemType: responseTypeArguments[0], itemType: responseTypeArguments[0],
}), }),
context: "response",
}) })
} }
} }
@@ -1126,6 +1129,7 @@ class OasKindGenerator extends FunctionKindGenerator {
allowedChildren, allowedChildren,
disallowedChildren, disallowedChildren,
zodObjectTypeName, zodObjectTypeName,
...rest
}: { }: {
/** /**
* The TypeScript type. * The TypeScript type.
@@ -1159,6 +1163,10 @@ class OasKindGenerator extends FunctionKindGenerator {
* generated type name. * generated type name.
*/ */
zodObjectTypeName?: string zodObjectTypeName?: string
/**
* Whether the type is in a request / response
*/
context?: "request" | "response"
}): OpenApiSchema { }): OpenApiSchema {
if (level > this.MAX_LEVEL) { if (level > this.MAX_LEVEL) {
return {} return {}
@@ -1183,7 +1191,8 @@ class OasKindGenerator extends FunctionKindGenerator {
{ {
title: title || typeAsString, title: title || typeAsString,
description, description,
} },
rest.context
) )
if (schemaFromFactory) { if (schemaFromFactory) {
@@ -1204,12 +1213,15 @@ class OasKindGenerator extends FunctionKindGenerator {
}) })
return { return {
type: "string", type: "string",
description,
enum: enumMembers, enum: enumMembers,
} }
case itemType.isLiteral(): case itemType.isLiteral() || typeAsString === "RegExp":
const isString =
itemType.flags === ts.TypeFlags.StringLiteral ||
typeAsString === "RegExp"
return { return {
type: type: isString
itemType.flags === ts.TypeFlags.StringLiteral
? "string" ? "string"
: itemType.flags === ts.TypeFlags.NumberLiteral : itemType.flags === ts.TypeFlags.NumberLiteral
? "number" ? "number"
@@ -1268,6 +1280,7 @@ class OasKindGenerator extends FunctionKindGenerator {
parentName: title || descriptionOptions?.parentName, parentName: title || descriptionOptions?.parentName,
} }
: undefined, : undefined,
...rest,
}), }),
} }
case itemType.isUnion(): case itemType.isUnion():
@@ -1279,13 +1292,16 @@ class OasKindGenerator extends FunctionKindGenerator {
if (allLiteral) { if (allLiteral) {
return { return {
type: "string", type: "string",
description,
enum: (itemType as ts.UnionType).types.map( enum: (itemType as ts.UnionType).types.map(
(unionType) => (unionType as ts.LiteralType).value (unionType) => (unionType as ts.LiteralType).value
), ),
} }
} }
return {
oneOf: (itemType as ts.UnionType).types.map((unionType) => const oneOfItems = this.removeStringRegExpTypeOverlaps(
(itemType as ts.UnionType).types
).map((unionType) =>
this.typeToSchema({ this.typeToSchema({
itemType: unionType, itemType: unionType,
// not incrementing considering the // not incrementing considering the
@@ -1294,13 +1310,21 @@ class OasKindGenerator extends FunctionKindGenerator {
level, level,
title, title,
descriptionOptions, descriptionOptions,
...rest,
}) })
), )
if (oneOfItems.length === 1) {
return oneOfItems[0]
}
return {
oneOf: oneOfItems,
} }
case itemType.isIntersection(): case itemType.isIntersection():
return { const allOfItems = this.removeStringRegExpTypeOverlaps(
allOf: (itemType as ts.IntersectionType).types.map( (itemType as ts.IntersectionType).types
(intersectionType) => { ).map((intersectionType) => {
return this.typeToSchema({ return this.typeToSchema({
itemType: intersectionType, itemType: intersectionType,
// not incrementing considering the // not incrementing considering the
@@ -1309,9 +1333,16 @@ class OasKindGenerator extends FunctionKindGenerator {
level, level,
title, title,
descriptionOptions, descriptionOptions,
...rest,
}) })
})
if (allOfItems.length === 1) {
return allOfItems[0]
} }
),
return {
allOf: allOfItems,
} }
case typeAsString.startsWith("Pick"): case typeAsString.startsWith("Pick"):
const pickTypeArgs = const pickTypeArgs =
@@ -1332,6 +1363,7 @@ class OasKindGenerator extends FunctionKindGenerator {
level, level,
descriptionOptions, descriptionOptions,
allowedChildren: pickedProperties, allowedChildren: pickedProperties,
...rest,
}) })
case typeAsString.startsWith("Omit"): case typeAsString.startsWith("Omit"):
const omitTypeArgs = const omitTypeArgs =
@@ -1352,6 +1384,7 @@ class OasKindGenerator extends FunctionKindGenerator {
level, level,
descriptionOptions, descriptionOptions,
disallowedChildren: omitProperties, disallowedChildren: omitProperties,
...rest,
}) })
case typeAsString.startsWith("Partial"): case typeAsString.startsWith("Partial"):
const typeArg = const typeArg =
@@ -1368,6 +1401,7 @@ class OasKindGenerator extends FunctionKindGenerator {
descriptionOptions, descriptionOptions,
disallowedChildren, disallowedChildren,
allowedChildren, allowedChildren,
...rest,
}) })
// remove all required items // remove all required items
@@ -1405,6 +1439,7 @@ class OasKindGenerator extends FunctionKindGenerator {
typeStr: property.name, typeStr: property.name,
parentName: title || descriptionOptions?.parentName, parentName: title || descriptionOptions?.parentName,
}, },
...rest,
}) })
if (isDeleteResponse && property.name === "object") { if (isDeleteResponse && property.name === "object") {
@@ -2034,6 +2069,23 @@ class OasKindGenerator extends FunctionKindGenerator {
fnText.includes(indicator) 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 export default OasKindGenerator