docs-utils: fix auth detection + added query params (#8997)

This commit is contained in:
Shahed Nasser authored and GitHub committed 2024-09-04 19:36:10 +03:00
1 parent 13e4f4aeb5
commit 090cb36543
1 file changed
+82 -20
@@ -42,6 +42,12 @@ export type OasArea = "admin" | "store"
type ParameterType = "query" | "path" type ParameterType = "query" | "path"
type AuthRequests = {
exact?: string
startsWith?: string
requiresAuthentication: boolean
}
/** /**
* OAS generator for API routes. It extends the {@link FunctionKindGenerator} * OAS generator for API routes. It extends the {@link FunctionKindGenerator}
* since API routes are functions. * since API routes are functions.
@@ -55,7 +61,21 @@ class OasKindGenerator extends FunctionKindGenerator {
"RequestWithContext", "RequestWithContext",
"AuthenticatedMedusaRequest", "AuthenticatedMedusaRequest",
] ]
// as it's not always possible to detect authenticated request
// use this to override the default detection logic.
readonly AUTH_REQUESTS: AuthRequests[] = [
{
exact: "store/orders",
requiresAuthentication: true,
},
{
startsWith: "store/customers/me",
requiresAuthentication: true,
},
]
readonly RESPONSE_TYPE_NAMES = ["MedusaResponse"] readonly RESPONSE_TYPE_NAMES = ["MedusaResponse"]
readonly FIELD_QUERY_PARAMS = ["fields", "expand"]
readonly PAGINATION_QUERY_PARAMS = ["limit", "offset", "order"]
/** /**
* 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,
@@ -452,6 +472,10 @@ class OasKindGenerator extends FunctionKindGenerator {
type: "query", type: "query",
}) })
if (!oas.parameters.length) {
oas.parameters = undefined
}
// update request schema // update request schema
const existingRequestBodySchema = ( const existingRequestBodySchema = (
oas.requestBody as OpenAPIV3.RequestBodyObject oas.requestBody as OpenAPIV3.RequestBodyObject
@@ -461,7 +485,10 @@ class OasKindGenerator extends FunctionKindGenerator {
newSchema: requestSchema, newSchema: requestSchema,
}) })
if (!updatedRequestSchema) { if (
!updatedRequestSchema ||
Object.keys(updatedRequestSchema).length === 0
) {
// if there's no request schema, remove it from the OAS // if there's no request schema, remove it from the OAS
delete oas.requestBody delete oas.requestBody
} else { } else {
@@ -531,6 +558,7 @@ class OasKindGenerator extends FunctionKindGenerator {
delete oas.responses![oldResponseStatus] delete oas.responses![oldResponseStatus]
} }
if (updatedResponseSchema && Object.keys(updatedResponseSchema).length) {
// update the response schema // update the response schema
oas.responses![newStatus] = { oas.responses![newStatus] = {
description: "OK", description: "OK",
@@ -540,11 +568,17 @@ class OasKindGenerator extends FunctionKindGenerator {
? this.oasSchemaHelper.namedSchemaToReference( ? this.oasSchemaHelper.namedSchemaToReference(
updatedResponseSchema updatedResponseSchema
) || ) ||
this.oasSchemaHelper.schemaChildrenToRefs(updatedResponseSchema) this.oasSchemaHelper.schemaChildrenToRefs(
updatedResponseSchema
)
: updatedResponseSchema, : updatedResponseSchema,
}, },
}, },
} }
} else if (oldResponseStatus) {
// delete the old response schema
delete oas.responses![oldResponseStatus]
}
} }
// update examples if the --generate-examples option is enabled // update examples if the --generate-examples option is enabled
@@ -733,11 +767,21 @@ class OasKindGenerator extends FunctionKindGenerator {
.statements.some((statement) => .statements.some((statement) =>
statement.getText().includes("AUTHENTICATE = false") statement.getText().includes("AUTHENTICATE = false")
) )
const hasAuthenticationOverride =
this.AUTH_REQUESTS.find((authRequest) => {
return (
authRequest.exact === oasPath ||
(authRequest.startsWith && oasPath.startsWith(authRequest.startsWith))
)
})?.requiresAuthentication === true
const isAdminAuthenticated = const isAdminAuthenticated =
!isAuthenticationDisabled && oasPath.startsWith("admin") (!isAuthenticationDisabled || hasAuthenticationOverride) &&
const isStoreAuthenticated = oasPath.startsWith("admin")
!isAuthenticationDisabled && oasPath.startsWith("store/customers/me") const isStoreAuthenticated = hasAuthenticationOverride
const isAuthenticated = isAdminAuthenticated || isStoreAuthenticated ? oasPath.startsWith("store")
: !isAuthenticationDisabled && oasPath.startsWith("store")
const isAuthenticated =
isAdminAuthenticated || isStoreAuthenticated || hasAuthenticationOverride
return { return {
isAdminAuthenticated, isAdminAuthenticated,
@@ -958,13 +1002,26 @@ class OasKindGenerator extends FunctionKindGenerator {
// TODO for now I'll use the type for validatedQuery until // TODO for now I'll use the type for validatedQuery until
// we have an actual approach to infer query types // we have an actual approach to infer query types
const querySymbol = requestType.getProperty("validatedQuery") const querySymbol = requestType.getProperty("validatedQuery")
if (querySymbol && this.shouldAddQueryParams(node)) { if (querySymbol) {
const { shouldAddFields, shouldAddPagination } =
this.shouldAddQueryParams(node)
const queryType = this.checker.getTypeOfSymbol(querySymbol) const queryType = this.checker.getTypeOfSymbol(querySymbol)
const queryTypeName = this.checker.typeToString(queryType) const queryTypeName = this.checker.typeToString(queryType)
queryType.getProperties().forEach((property) => { queryType.getProperties().forEach((property) => {
const propertyName = property.getName()
// if this is a field / pagination query parameter and
// they're not used in the route, don't add them.
if (
(this.FIELD_QUERY_PARAMS.includes(propertyName) &&
!shouldAddFields) ||
(this.PAGINATION_QUERY_PARAMS.includes(propertyName) &&
!shouldAddPagination)
) {
return
}
const propertyType = this.checker.getTypeOfSymbol(property) const propertyType = this.checker.getTypeOfSymbol(property)
const descriptionOptions: SchemaDescriptionOptions = { const descriptionOptions: SchemaDescriptionOptions = {
typeStr: property.getName(), typeStr: propertyName,
parentName: tagName, parentName: tagName,
rawParentName: queryTypeName, rawParentName: queryTypeName,
node: property.valueDeclaration, node: property.valueDeclaration,
@@ -973,13 +1030,13 @@ class OasKindGenerator extends FunctionKindGenerator {
} }
parameters.push( parameters.push(
this.getParameterObject({ this.getParameterObject({
name: property.getName(), name: propertyName,
type: "query", type: "query",
description: this.getSchemaDescription(descriptionOptions), description: this.getSchemaDescription(descriptionOptions),
required: this.isRequired(property), required: this.isRequired(property),
schema: this.typeToSchema({ schema: this.typeToSchema({
itemType: propertyType, itemType: propertyType,
title: property.getName(), title: propertyName,
descriptionOptions, descriptionOptions,
context: "request", context: "request",
}), }),
@@ -2062,15 +2119,16 @@ class OasKindGenerator extends FunctionKindGenerator {
} }
} }
shouldAddQueryParams(node: FunctionNode): boolean { shouldAddQueryParams(node: FunctionNode): {
const queryParamsUsageIndicators = [ shouldAddFields: boolean
`req.filterableFields`, shouldAddPagination: boolean
`req.remoteQueryConfig`, } {
]
const fnText = node.getText() const fnText = node.getText()
return queryParamsUsageIndicators.some((indicator) =>
fnText.includes(indicator) return {
) shouldAddFields: fnText.includes(`req.remoteQueryConfig.fields`),
shouldAddPagination: fnText.includes(`req.remoteQueryConfig.pagination`),
}
} }
hasResponseType(node: FunctionNode, oas: OpenApiOperation): boolean { hasResponseType(node: FunctionNode, oas: OpenApiOperation): boolean {
@@ -2081,14 +2139,18 @@ class OasKindGenerator extends FunctionKindGenerator {
return false return false
} }
const responseContent = (oas.responses![oldResponseStatus] as OpenAPIV3.ResponseObject).content const responseContent = (
oas.responses![oldResponseStatus] as OpenAPIV3.ResponseObject
).content
if (!responseContent) { if (!responseContent) {
return false return false
} }
const fnText = node.getText() const fnText = node.getText()
return Object.keys(responseContent).some((responseType) => fnText.includes(responseType)) return Object.keys(responseContent).some((responseType) =>
fnText.includes(responseType)
)
} }
private removeStringRegExpTypeOverlaps(types: ts.Type[]): ts.Type[] { private removeStringRegExpTypeOverlaps(types: ts.Type[]): ts.Type[] {